@valentine-efagene/qshelter-common 2.0.75 → 2.0.76

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.
Files changed (36) hide show
  1. package/dist/generated/client/browser.d.ts +5 -5
  2. package/dist/generated/client/client.d.ts +5 -5
  3. package/dist/generated/client/internal/class.d.ts +11 -11
  4. package/dist/generated/client/internal/class.js +2 -2
  5. package/dist/generated/client/internal/prismaNamespace.d.ts +95 -95
  6. package/dist/generated/client/internal/prismaNamespace.js +25 -25
  7. package/dist/generated/client/internal/prismaNamespaceBrowser.d.ts +27 -27
  8. package/dist/generated/client/internal/prismaNamespaceBrowser.js +25 -25
  9. package/dist/generated/client/models/Contract.d.ts +155 -155
  10. package/dist/generated/client/models/index.d.ts +3 -0
  11. package/dist/generated/client/models/index.js +3 -0
  12. package/dist/generated/client/models.d.ts +1 -1
  13. package/dist/src/events/bus/event-bus.service.d.ts +84 -0
  14. package/dist/src/events/bus/event-bus.service.js +372 -0
  15. package/dist/src/events/bus/event-bus.types.d.ts +73 -0
  16. package/dist/src/events/bus/event-bus.types.js +22 -0
  17. package/dist/src/events/index.d.ts +5 -6
  18. package/dist/src/events/index.js +7 -8
  19. package/dist/src/events/notifications/event-publisher.d.ts +41 -0
  20. package/dist/src/events/notifications/event-publisher.js +111 -0
  21. package/dist/src/events/notifications/notification-enums.d.ts +46 -0
  22. package/dist/src/events/notifications/notification-enums.js +59 -0
  23. package/dist/src/events/notifications/notification-event.d.ts +76 -0
  24. package/dist/src/events/notifications/notification-event.js +1 -0
  25. package/dist/src/events/unified/unified-event.service.d.ts +157 -0
  26. package/dist/src/events/unified/unified-event.service.js +177 -0
  27. package/dist/src/events/workflow/event-config.service.d.ts +123 -0
  28. package/dist/src/events/workflow/event-config.service.js +416 -0
  29. package/dist/src/events/workflow/event-seeder.d.ts +80 -0
  30. package/dist/src/events/workflow/event-seeder.js +343 -0
  31. package/dist/src/events/workflow/workflow-event.service.d.ts +230 -0
  32. package/dist/src/events/workflow/workflow-event.service.js +682 -0
  33. package/dist/src/events/workflow/workflow-types.d.ts +364 -0
  34. package/dist/src/events/workflow/workflow-types.js +22 -0
  35. package/package.json +4 -1
  36. package/prisma/schema.prisma +87 -79
@@ -0,0 +1,111 @@
1
+ import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
2
+ import { NotificationChannel } from './notification-enums';
3
+ /**
4
+ * Get SNS client configured for LocalStack or AWS
5
+ */
6
+ function createSNSClient(config) {
7
+ const isLocalStack = process.env.LOCALSTACK_ENDPOINT || process.env.NODE_ENV === 'test';
8
+ const clientConfig = {
9
+ region: config.region || process.env.AWS_REGION || 'us-east-1',
10
+ };
11
+ if (isLocalStack) {
12
+ clientConfig.endpoint = config.endpoint || process.env.LOCALSTACK_ENDPOINT || 'http://localhost:4566';
13
+ clientConfig.credentials = {
14
+ accessKeyId: 'test',
15
+ secretAccessKey: 'test',
16
+ };
17
+ }
18
+ return new SNSClient(clientConfig);
19
+ }
20
+ /**
21
+ * Event Publisher for sending notification events to SNS
22
+ * Used by all services to publish events to the notifications topic
23
+ */
24
+ export class EventPublisher {
25
+ snsClient;
26
+ topicArn;
27
+ serviceName;
28
+ constructor(serviceName, config) {
29
+ this.serviceName = serviceName;
30
+ this.snsClient = createSNSClient(config || {});
31
+ // Topic ARN can be passed directly or constructed from env vars
32
+ const stage = process.env.STAGE || process.env.NODE_ENV || 'test';
33
+ const region = config?.region || process.env.AWS_REGION || 'us-east-1';
34
+ const accountId = process.env.AWS_ACCOUNT_ID || '000000000000';
35
+ this.topicArn = config?.topicArn ||
36
+ process.env.NOTIFICATIONS_TOPIC_ARN ||
37
+ `arn:aws:sns:${region}:${accountId}:qshelter-${stage}-notifications`;
38
+ }
39
+ /**
40
+ * Publish a notification event to SNS
41
+ */
42
+ async publish(type, channel, payload, meta) {
43
+ const event = {
44
+ type,
45
+ channel,
46
+ payload,
47
+ meta: {
48
+ source: this.serviceName,
49
+ timestamp: new Date().toISOString(),
50
+ correlationId: meta?.correlationId || crypto.randomUUID(),
51
+ userId: meta?.userId,
52
+ tenantId: meta?.tenantId,
53
+ },
54
+ };
55
+ const command = new PublishCommand({
56
+ TopicArn: this.topicArn,
57
+ Message: JSON.stringify(event),
58
+ MessageAttributes: {
59
+ notificationType: {
60
+ DataType: 'String',
61
+ StringValue: type,
62
+ },
63
+ channel: {
64
+ DataType: 'String',
65
+ StringValue: channel,
66
+ },
67
+ source: {
68
+ DataType: 'String',
69
+ StringValue: this.serviceName,
70
+ },
71
+ },
72
+ });
73
+ const result = await this.snsClient.send(command);
74
+ console.log(`[EventPublisher] Published ${type} event to SNS`, {
75
+ messageId: result.MessageId,
76
+ type,
77
+ channel,
78
+ source: this.serviceName,
79
+ });
80
+ return result.MessageId || '';
81
+ }
82
+ /**
83
+ * Convenience method for publishing email notifications
84
+ */
85
+ async publishEmail(type, payload, meta) {
86
+ return this.publish(type, NotificationChannel.EMAIL, payload, meta);
87
+ }
88
+ /**
89
+ * Convenience method for publishing SMS notifications
90
+ */
91
+ async publishSMS(type, payload, meta) {
92
+ return this.publish(type, NotificationChannel.SMS, payload, meta);
93
+ }
94
+ /**
95
+ * Convenience method for publishing push notifications
96
+ */
97
+ async publishPush(type, payload, meta) {
98
+ return this.publish(type, NotificationChannel.PUSH, payload, meta);
99
+ }
100
+ }
101
+ // Singleton instances per service
102
+ const publisherInstances = new Map();
103
+ /**
104
+ * Get or create an EventPublisher for a service
105
+ */
106
+ export function getEventPublisher(serviceName, config) {
107
+ if (!publisherInstances.has(serviceName)) {
108
+ publisherInstances.set(serviceName, new EventPublisher(serviceName, config));
109
+ }
110
+ return publisherInstances.get(serviceName);
111
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Types of notifications that can be sent
3
+ * Used by all services to ensure consistent event naming
4
+ */
5
+ export declare enum NotificationType {
6
+ WELCOME = "welcome",
7
+ VERIFY_EMAIL = "verifyEmail",
8
+ ACCOUNT_VERIFIED = "accountVerified",
9
+ ACCOUNT_SUSPENDED = "accountSuspended",
10
+ PASSWORD_RESET = "resetPassword",
11
+ MISSED_PAYMENT = "missedPayments",
12
+ WALLET_TOP_UP = "walletTopUp",
13
+ PAYMENT_RECEIVED = "paymentReceived",
14
+ PAYMENT_FAILED = "paymentFailed",
15
+ PAYMENT_REMINDER = "paymentReminder",
16
+ PROPERTY_ALLOCATION = "propertyAllocation",
17
+ UPDATED_TERMS = "updatedTermsAndConditions",
18
+ PREQUALIFICATION_SUBMITTED = "prequalificationSubmitted",
19
+ PREQUALIFICATION_APPROVED = "prequalificationApproved",
20
+ PREQUALIFICATION_REJECTED = "prequalificationRejected",
21
+ CONTRACT_CREATED = "contractCreated",
22
+ CONTRACT_ACTIVATED = "contractActivated",
23
+ CONTRACT_TERMINATION_REQUESTED = "contractTerminationRequested",
24
+ CONTRACT_TERMINATION_APPROVED = "contractTerminationApproved",
25
+ CONTRACT_TERMINATED = "contractTerminated",
26
+ OFFER_LETTER_SENT = "offerLetterSent",
27
+ OFFER_LETTER_SIGNED = "offerLetterSigned",
28
+ OFFER_LETTER_EXPIRED = "offerLetterExpired",
29
+ UNDERWRITING_APPROVED = "underwritingApproved",
30
+ UNDERWRITING_REJECTED = "underwritingRejected",
31
+ UNDERWRITING_CONDITIONAL = "underwritingConditional",
32
+ DOCUMENT_APPROVED = "documentApproved",
33
+ DOCUMENT_REJECTED = "documentRejected",
34
+ ADMIN_CONTRIBUTION_RECEIVED = "adminContributionReceived",
35
+ ADMIN_PROPERTY_ALLOCATION = "adminPropertyAllocation",
36
+ ADMIN_INVITE = "adminInviteAdmin",
37
+ OTP = "otp"
38
+ }
39
+ /**
40
+ * Channels through which notifications can be delivered
41
+ */
42
+ export declare enum NotificationChannel {
43
+ EMAIL = "email",
44
+ SMS = "sms",
45
+ PUSH = "push"
46
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Types of notifications that can be sent
3
+ * Used by all services to ensure consistent event naming
4
+ */
5
+ export var NotificationType;
6
+ (function (NotificationType) {
7
+ // User lifecycle
8
+ NotificationType["WELCOME"] = "welcome";
9
+ NotificationType["VERIFY_EMAIL"] = "verifyEmail";
10
+ NotificationType["ACCOUNT_VERIFIED"] = "accountVerified";
11
+ NotificationType["ACCOUNT_SUSPENDED"] = "accountSuspended";
12
+ NotificationType["PASSWORD_RESET"] = "resetPassword";
13
+ // Payments
14
+ NotificationType["MISSED_PAYMENT"] = "missedPayments";
15
+ NotificationType["WALLET_TOP_UP"] = "walletTopUp";
16
+ NotificationType["PAYMENT_RECEIVED"] = "paymentReceived";
17
+ NotificationType["PAYMENT_FAILED"] = "paymentFailed";
18
+ NotificationType["PAYMENT_REMINDER"] = "paymentReminder";
19
+ // Property
20
+ NotificationType["PROPERTY_ALLOCATION"] = "propertyAllocation";
21
+ // Terms
22
+ NotificationType["UPDATED_TERMS"] = "updatedTermsAndConditions";
23
+ // Prequalification
24
+ NotificationType["PREQUALIFICATION_SUBMITTED"] = "prequalificationSubmitted";
25
+ NotificationType["PREQUALIFICATION_APPROVED"] = "prequalificationApproved";
26
+ NotificationType["PREQUALIFICATION_REJECTED"] = "prequalificationRejected";
27
+ // Contract
28
+ NotificationType["CONTRACT_CREATED"] = "contractCreated";
29
+ NotificationType["CONTRACT_ACTIVATED"] = "contractActivated";
30
+ NotificationType["CONTRACT_TERMINATION_REQUESTED"] = "contractTerminationRequested";
31
+ NotificationType["CONTRACT_TERMINATION_APPROVED"] = "contractTerminationApproved";
32
+ NotificationType["CONTRACT_TERMINATED"] = "contractTerminated";
33
+ // Offer Letters
34
+ NotificationType["OFFER_LETTER_SENT"] = "offerLetterSent";
35
+ NotificationType["OFFER_LETTER_SIGNED"] = "offerLetterSigned";
36
+ NotificationType["OFFER_LETTER_EXPIRED"] = "offerLetterExpired";
37
+ // Underwriting
38
+ NotificationType["UNDERWRITING_APPROVED"] = "underwritingApproved";
39
+ NotificationType["UNDERWRITING_REJECTED"] = "underwritingRejected";
40
+ NotificationType["UNDERWRITING_CONDITIONAL"] = "underwritingConditional";
41
+ // Documents
42
+ NotificationType["DOCUMENT_APPROVED"] = "documentApproved";
43
+ NotificationType["DOCUMENT_REJECTED"] = "documentRejected";
44
+ // Admin
45
+ NotificationType["ADMIN_CONTRIBUTION_RECEIVED"] = "adminContributionReceived";
46
+ NotificationType["ADMIN_PROPERTY_ALLOCATION"] = "adminPropertyAllocation";
47
+ NotificationType["ADMIN_INVITE"] = "adminInviteAdmin";
48
+ // OTP
49
+ NotificationType["OTP"] = "otp";
50
+ })(NotificationType || (NotificationType = {}));
51
+ /**
52
+ * Channels through which notifications can be delivered
53
+ */
54
+ export var NotificationChannel;
55
+ (function (NotificationChannel) {
56
+ NotificationChannel["EMAIL"] = "email";
57
+ NotificationChannel["SMS"] = "sms";
58
+ NotificationChannel["PUSH"] = "push";
59
+ })(NotificationChannel || (NotificationChannel = {}));
@@ -0,0 +1,76 @@
1
+ import { NotificationType, NotificationChannel } from './notification-enums';
2
+ /**
3
+ * Metadata attached to every notification event for tracing and debugging
4
+ */
5
+ export interface NotificationMeta {
6
+ /** Service that published the event */
7
+ source: string;
8
+ /** ISO timestamp of when event was created */
9
+ timestamp: string;
10
+ /** Correlation ID for distributed tracing */
11
+ correlationId?: string;
12
+ /** User ID if applicable */
13
+ userId?: string;
14
+ /** Tenant ID if applicable */
15
+ tenantId?: string;
16
+ }
17
+ /**
18
+ * Standard notification event payload sent via SNS->SQS
19
+ * All services must use this interface when publishing notification events
20
+ */
21
+ export interface NotificationEvent<T = Record<string, unknown>> {
22
+ /** Type of notification - determines which template/handler to use */
23
+ type: NotificationType;
24
+ /** Delivery channel - email, sms, or push */
25
+ channel: NotificationChannel;
26
+ /** The actual notification data (varies by type) */
27
+ payload: T;
28
+ /** Event metadata for tracing */
29
+ meta: NotificationMeta;
30
+ }
31
+ /**
32
+ * Email-specific payload fields that most email templates need
33
+ */
34
+ export interface EmailPayload {
35
+ /** Recipient email address */
36
+ to_email: string;
37
+ /** Optional subject override (defaults to template title) */
38
+ subject?: string;
39
+ }
40
+ /**
41
+ * Verify email payload
42
+ */
43
+ export interface VerifyEmailPayload extends EmailPayload {
44
+ homeBuyerName: string;
45
+ verificationLink: string;
46
+ }
47
+ /**
48
+ * Password reset payload
49
+ */
50
+ export interface PasswordResetPayload extends EmailPayload {
51
+ homeBuyerName: string;
52
+ otp: string;
53
+ ttl: number;
54
+ }
55
+ /**
56
+ * Welcome email payload
57
+ */
58
+ export interface WelcomePayload extends EmailPayload {
59
+ homeBuyerName: string;
60
+ loginLink: string;
61
+ }
62
+ /**
63
+ * Missed payments payload
64
+ */
65
+ export interface MissedPaymentsPayload extends EmailPayload {
66
+ homeBuyerName: string;
67
+ amount: number;
68
+ loginLink: string;
69
+ }
70
+ /**
71
+ * Account verified payload
72
+ */
73
+ export interface AccountVerifiedPayload extends EmailPayload {
74
+ homeBuyerName: string;
75
+ loginLink: string;
76
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Unified Event Service
3
+ *
4
+ * This service bridges WorkflowEventService (database-backed) with EventBusService (transport layer).
5
+ * It provides a single, unified API for all event emission across services.
6
+ *
7
+ * Architecture:
8
+ * 1. Service emits event → UnifiedEventService
9
+ * 2. Event stored in DB → WorkflowEvent (audit trail)
10
+ * 3. Handlers fetched → EventHandler table (admin-configured)
11
+ * 4. EventBusService delivers → Multi-transport (HTTP/SNS/SQS/EventBridge)
12
+ * 5. Execution logged → EventHandlerExecution (tracking)
13
+ *
14
+ * Usage:
15
+ * ```typescript
16
+ * const eventService = createUnifiedEventService(prisma, eventBus);
17
+ *
18
+ * await eventService.emit(tenantId, {
19
+ * eventType: 'CONTRACT_CREATED',
20
+ * payload: { contractId, buyerId, propertyUnitId },
21
+ * source: 'contract-service',
22
+ * actor: { id: userId, type: 'USER' }
23
+ * });
24
+ * ```
25
+ */
26
+ import { PrismaClient } from '../../../generated/client/client';
27
+ import type { EmitEventInput, WorkflowEventData, ProcessEventResult } from '../workflow/workflow-types';
28
+ import { AutomationRegistry, ServiceRegistry } from '../workflow/workflow-event.service';
29
+ import { NotificationType, NotificationChannel } from '../notifications/notification-enums';
30
+ /**
31
+ * EventBusService interface
32
+ * Minimal interface to avoid circular dependencies with EventBusService
33
+ */
34
+ export interface IEventBusService {
35
+ publish<T = any>(eventType: string, data: T, options?: {
36
+ tenantId?: number;
37
+ source?: string;
38
+ metadata?: Record<string, any>;
39
+ correlationId?: string;
40
+ }): Promise<any[]>;
41
+ }
42
+ /**
43
+ * Configuration for UnifiedEventService
44
+ */
45
+ export interface UnifiedEventServiceConfig {
46
+ /**
47
+ * Whether to process events immediately (synchronous)
48
+ * or defer to background worker (asynchronous)
49
+ * Default: false (async)
50
+ */
51
+ processImmediately?: boolean;
52
+ /**
53
+ * Whether to publish to EventBus after storing in DB
54
+ * Default: true
55
+ */
56
+ publishToEventBus?: boolean;
57
+ /**
58
+ * Whether to use legacy EventPublisher for backwards compatibility
59
+ * Default: false
60
+ */
61
+ useLegacyPublisher?: boolean;
62
+ /**
63
+ * Service name for event source attribution
64
+ */
65
+ serviceName?: string;
66
+ }
67
+ /**
68
+ * UnifiedEventService - Single API for all event emission
69
+ */
70
+ export declare class UnifiedEventService {
71
+ private prisma;
72
+ private eventBus?;
73
+ private config;
74
+ private workflowEventService;
75
+ private legacyPublisher?;
76
+ constructor(prisma: PrismaClient, eventBus?: IEventBusService | undefined, config?: UnifiedEventServiceConfig, automationRegistry?: AutomationRegistry, serviceRegistry?: ServiceRegistry);
77
+ /**
78
+ * Emit an event through the unified system
79
+ *
80
+ * This method:
81
+ * 1. Stores event in WorkflowEvent table (audit trail)
82
+ * 2. Fetches admin-configured handlers from EventHandler table
83
+ * 3. Executes handlers (optionally via EventBus)
84
+ * 4. Logs execution results in EventHandlerExecution table
85
+ *
86
+ * @param tenantId - Tenant context
87
+ * @param input - Event details
88
+ * @returns The created event data
89
+ */
90
+ emit(tenantId: string, input: EmitEventInput): Promise<WorkflowEventData>;
91
+ /**
92
+ * Process an event (run all handlers)
93
+ *
94
+ * This is called either:
95
+ * - Immediately after emit() if processImmediately=true
96
+ * - By a background worker for async processing
97
+ *
98
+ * @param eventId - The event to process
99
+ * @returns Processing results
100
+ */
101
+ processEvent(eventId: string): Promise<ProcessEventResult>;
102
+ /**
103
+ * Register an automation handler
104
+ *
105
+ * Automations are custom business logic that can be triggered by events.
106
+ * Example: calculateLateFee, sendWelcomePackage, archiveContract
107
+ *
108
+ * @param name - Automation name (matches EventHandler.config.automationName)
109
+ * @param handler - The automation function
110
+ */
111
+ registerAutomation(name: string, handler: (inputs: Record<string, unknown>, tenantId: string) => Promise<unknown>): void;
112
+ /**
113
+ * Convenience method: Emit notification event
114
+ */
115
+ publishNotification(params: {
116
+ type: NotificationType;
117
+ channel: NotificationChannel;
118
+ payload: Record<string, unknown>;
119
+ meta: {
120
+ source: string;
121
+ tenantId?: string;
122
+ correlationId?: string;
123
+ userId?: string;
124
+ };
125
+ }): Promise<void>;
126
+ /**
127
+ * Convenience method: Emit email notification
128
+ */
129
+ publishEmail(type: NotificationType, payload: Record<string, unknown>, meta: {
130
+ source: string;
131
+ tenantId?: string;
132
+ correlationId?: string;
133
+ userId?: string;
134
+ }): Promise<void>;
135
+ /**
136
+ * Convenience method: Emit SMS notification
137
+ */
138
+ publishSms(type: NotificationType, payload: Record<string, unknown>, meta: {
139
+ source: string;
140
+ tenantId?: string;
141
+ correlationId?: string;
142
+ userId?: string;
143
+ }): Promise<void>;
144
+ }
145
+ /**
146
+ * Factory function to create UnifiedEventService
147
+ *
148
+ * @param prisma - Prisma client
149
+ * @param eventBus - Optional EventBus service for transport-layer delivery
150
+ * @param config - Configuration options
151
+ * @returns Configured UnifiedEventService instance
152
+ */
153
+ export declare function createUnifiedEventService(prisma: PrismaClient, eventBus?: IEventBusService, config?: UnifiedEventServiceConfig): UnifiedEventService;
154
+ /**
155
+ * Export for backwards compatibility
156
+ */
157
+ export { NotificationType, NotificationChannel };
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Unified Event Service
3
+ *
4
+ * This service bridges WorkflowEventService (database-backed) with EventBusService (transport layer).
5
+ * It provides a single, unified API for all event emission across services.
6
+ *
7
+ * Architecture:
8
+ * 1. Service emits event → UnifiedEventService
9
+ * 2. Event stored in DB → WorkflowEvent (audit trail)
10
+ * 3. Handlers fetched → EventHandler table (admin-configured)
11
+ * 4. EventBusService delivers → Multi-transport (HTTP/SNS/SQS/EventBridge)
12
+ * 5. Execution logged → EventHandlerExecution (tracking)
13
+ *
14
+ * Usage:
15
+ * ```typescript
16
+ * const eventService = createUnifiedEventService(prisma, eventBus);
17
+ *
18
+ * await eventService.emit(tenantId, {
19
+ * eventType: 'CONTRACT_CREATED',
20
+ * payload: { contractId, buyerId, propertyUnitId },
21
+ * source: 'contract-service',
22
+ * actor: { id: userId, type: 'USER' }
23
+ * });
24
+ * ```
25
+ */
26
+ import { WorkflowEventService } from '../workflow/workflow-event.service';
27
+ import { EventPublisher } from '../notifications/event-publisher';
28
+ import { NotificationType, NotificationChannel } from '../notifications/notification-enums';
29
+ /**
30
+ * UnifiedEventService - Single API for all event emission
31
+ */
32
+ export class UnifiedEventService {
33
+ prisma;
34
+ eventBus;
35
+ config;
36
+ workflowEventService;
37
+ legacyPublisher;
38
+ constructor(prisma, eventBus, config = {}, automationRegistry, serviceRegistry) {
39
+ this.prisma = prisma;
40
+ this.eventBus = eventBus;
41
+ this.config = config;
42
+ // Initialize WorkflowEventService (database-backed)
43
+ this.workflowEventService = new WorkflowEventService(prisma, automationRegistry, config.useLegacyPublisher ? new EventPublisher(config.serviceName || 'unified-event-service') : undefined);
44
+ // Optionally initialize legacy publisher for backwards compatibility
45
+ if (config.useLegacyPublisher) {
46
+ this.legacyPublisher = new EventPublisher(config.serviceName || 'unified-event-service');
47
+ }
48
+ }
49
+ /**
50
+ * Emit an event through the unified system
51
+ *
52
+ * This method:
53
+ * 1. Stores event in WorkflowEvent table (audit trail)
54
+ * 2. Fetches admin-configured handlers from EventHandler table
55
+ * 3. Executes handlers (optionally via EventBus)
56
+ * 4. Logs execution results in EventHandlerExecution table
57
+ *
58
+ * @param tenantId - Tenant context
59
+ * @param input - Event details
60
+ * @returns The created event data
61
+ */
62
+ async emit(tenantId, input) {
63
+ const processImmediately = this.config.processImmediately ?? false;
64
+ // 1. Store event in database (WorkflowEvent)
65
+ const event = await this.workflowEventService.emit(tenantId, input, false);
66
+ // 2. Optionally publish to EventBus for transport-layer delivery
67
+ if (this.config.publishToEventBus !== false && this.eventBus) {
68
+ try {
69
+ await this.eventBus.publish(input.eventType, input.payload, {
70
+ tenantId: Number(tenantId),
71
+ source: input.source,
72
+ correlationId: input.correlationId || event.id,
73
+ metadata: {
74
+ eventId: event.id,
75
+ actorId: input.actor?.id,
76
+ actorType: input.actor?.type,
77
+ },
78
+ });
79
+ }
80
+ catch (error) {
81
+ console.error('Failed to publish to EventBus:', error);
82
+ // Don't fail the entire operation if EventBus publish fails
83
+ // The event is already in the database and can be retried
84
+ }
85
+ }
86
+ // 3. Process event handlers (if immediate processing is enabled)
87
+ if (processImmediately) {
88
+ await this.processEvent(event.id);
89
+ }
90
+ return event;
91
+ }
92
+ /**
93
+ * Process an event (run all handlers)
94
+ *
95
+ * This is called either:
96
+ * - Immediately after emit() if processImmediately=true
97
+ * - By a background worker for async processing
98
+ *
99
+ * @param eventId - The event to process
100
+ * @returns Processing results
101
+ */
102
+ async processEvent(eventId) {
103
+ return this.workflowEventService.processEvent(eventId);
104
+ }
105
+ /**
106
+ * Register an automation handler
107
+ *
108
+ * Automations are custom business logic that can be triggered by events.
109
+ * Example: calculateLateFee, sendWelcomePackage, archiveContract
110
+ *
111
+ * @param name - Automation name (matches EventHandler.config.automationName)
112
+ * @param handler - The automation function
113
+ */
114
+ registerAutomation(name, handler) {
115
+ this.workflowEventService.registerAutomation(name, handler);
116
+ }
117
+ /**
118
+ * Convenience method: Emit notification event
119
+ */
120
+ async publishNotification(params) {
121
+ const tenantId = params.meta.tenantId;
122
+ if (!tenantId) {
123
+ throw new Error('tenantId is required for publishNotification');
124
+ }
125
+ // Map legacy notification to WorkflowEvent
126
+ await this.emit(tenantId, {
127
+ eventType: `NOTIFICATION_${params.type}`,
128
+ payload: {
129
+ notificationType: params.type,
130
+ channel: params.channel,
131
+ ...params.payload,
132
+ },
133
+ source: params.meta.source,
134
+ actor: params.meta.userId
135
+ ? { id: params.meta.userId, type: 'USER' }
136
+ : undefined,
137
+ correlationId: params.meta.correlationId,
138
+ });
139
+ }
140
+ /**
141
+ * Convenience method: Emit email notification
142
+ */
143
+ async publishEmail(type, payload, meta) {
144
+ return this.publishNotification({
145
+ type,
146
+ channel: NotificationChannel.EMAIL,
147
+ payload,
148
+ meta,
149
+ });
150
+ }
151
+ /**
152
+ * Convenience method: Emit SMS notification
153
+ */
154
+ async publishSms(type, payload, meta) {
155
+ return this.publishNotification({
156
+ type,
157
+ channel: NotificationChannel.SMS,
158
+ payload,
159
+ meta,
160
+ });
161
+ }
162
+ }
163
+ /**
164
+ * Factory function to create UnifiedEventService
165
+ *
166
+ * @param prisma - Prisma client
167
+ * @param eventBus - Optional EventBus service for transport-layer delivery
168
+ * @param config - Configuration options
169
+ * @returns Configured UnifiedEventService instance
170
+ */
171
+ export function createUnifiedEventService(prisma, eventBus, config = {}) {
172
+ return new UnifiedEventService(prisma, eventBus, config);
173
+ }
174
+ /**
175
+ * Export for backwards compatibility
176
+ */
177
+ export { NotificationType, NotificationChannel };