@valentine-efagene/qshelter-common 2.0.74 → 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.
- package/dist/generated/client/browser.d.ts +5 -5
- package/dist/generated/client/client.d.ts +5 -5
- package/dist/generated/client/internal/class.d.ts +11 -11
- package/dist/generated/client/internal/class.js +2 -2
- package/dist/generated/client/internal/prismaNamespace.d.ts +95 -95
- package/dist/generated/client/internal/prismaNamespace.js +25 -25
- package/dist/generated/client/internal/prismaNamespaceBrowser.d.ts +27 -27
- package/dist/generated/client/internal/prismaNamespaceBrowser.js +25 -25
- package/dist/generated/client/models/Contract.d.ts +155 -155
- package/dist/generated/client/models/index.d.ts +3 -0
- package/dist/generated/client/models/index.js +3 -0
- package/dist/generated/client/models.d.ts +1 -1
- package/dist/src/events/bus/event-bus.service.d.ts +84 -0
- package/dist/src/events/bus/event-bus.service.js +372 -0
- package/dist/src/events/bus/event-bus.types.d.ts +73 -0
- package/dist/src/events/bus/event-bus.types.js +22 -0
- package/dist/src/events/index.d.ts +5 -6
- package/dist/src/events/index.js +7 -8
- package/dist/src/events/notifications/event-publisher.d.ts +41 -0
- package/dist/src/events/notifications/event-publisher.js +111 -0
- package/dist/src/events/notifications/notification-enums.d.ts +46 -0
- package/dist/src/events/notifications/notification-enums.js +59 -0
- package/dist/src/events/notifications/notification-event.d.ts +76 -0
- package/dist/src/events/notifications/notification-event.js +1 -0
- package/dist/src/events/unified/unified-event.service.d.ts +157 -0
- package/dist/src/events/unified/unified-event.service.js +177 -0
- package/dist/src/events/workflow/event-config.service.d.ts +123 -0
- package/dist/src/events/workflow/event-config.service.js +416 -0
- package/dist/src/events/workflow/event-seeder.d.ts +80 -0
- package/dist/src/events/workflow/event-seeder.js +343 -0
- package/dist/src/events/workflow/workflow-event.service.d.ts +230 -0
- package/dist/src/events/workflow/workflow-event.service.js +682 -0
- package/dist/src/events/workflow/workflow-types.d.ts +364 -0
- package/dist/src/events/workflow/workflow-types.js +22 -0
- package/package.json +4 -1
- 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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 };
|