@axova/shared 1.0.0
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/CONFIGURATION_GUIDE.md +1 -0
- package/README.md +384 -0
- package/SCHEMA_ORGANIZATION.md +209 -0
- package/dist/configs/index.d.ts +85 -0
- package/dist/configs/index.js +555 -0
- package/dist/events/kafka.d.ts +40 -0
- package/dist/events/kafka.js +311 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +41 -0
- package/dist/interfaces/customer-events.d.ts +85 -0
- package/dist/interfaces/customer-events.js +2 -0
- package/dist/interfaces/inventory-events.d.ts +453 -0
- package/dist/interfaces/inventory-events.js +3 -0
- package/dist/interfaces/inventory-types.d.ts +894 -0
- package/dist/interfaces/inventory-types.js +3 -0
- package/dist/interfaces/order-events.d.ts +320 -0
- package/dist/interfaces/order-events.js +3 -0
- package/dist/lib/auditLogger.d.ts +162 -0
- package/dist/lib/auditLogger.js +626 -0
- package/dist/lib/authOrganization.d.ts +24 -0
- package/dist/lib/authOrganization.js +110 -0
- package/dist/lib/db.d.ts +6 -0
- package/dist/lib/db.js +88 -0
- package/dist/middleware/serviceAuth.d.ts +60 -0
- package/dist/middleware/serviceAuth.js +272 -0
- package/dist/middleware/storeOwnership.d.ts +15 -0
- package/dist/middleware/storeOwnership.js +156 -0
- package/dist/middleware/storeValidationMiddleware.d.ts +44 -0
- package/dist/middleware/storeValidationMiddleware.js +180 -0
- package/dist/middleware/userAuth.d.ts +27 -0
- package/dist/middleware/userAuth.js +218 -0
- package/dist/schemas/admin/admin-schema.d.ts +741 -0
- package/dist/schemas/admin/admin-schema.js +111 -0
- package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +648 -0
- package/dist/schemas/ai-moderation/ai-moderation-schema.js +88 -0
- package/dist/schemas/common/common-schemas.d.ts +436 -0
- package/dist/schemas/common/common-schemas.js +94 -0
- package/dist/schemas/compliance/compliance-schema.d.ts +3388 -0
- package/dist/schemas/compliance/compliance-schema.js +472 -0
- package/dist/schemas/compliance/kyc-schema.d.ts +2642 -0
- package/dist/schemas/compliance/kyc-schema.js +361 -0
- package/dist/schemas/customer/customer-schema.d.ts +2727 -0
- package/dist/schemas/customer/customer-schema.js +399 -0
- package/dist/schemas/index.d.ts +27 -0
- package/dist/schemas/index.js +138 -0
- package/dist/schemas/inventory/inventory-tables.d.ts +9476 -0
- package/dist/schemas/inventory/inventory-tables.js +1470 -0
- package/dist/schemas/inventory/lot-tables.d.ts +3281 -0
- package/dist/schemas/inventory/lot-tables.js +608 -0
- package/dist/schemas/order/order-schema.d.ts +5825 -0
- package/dist/schemas/order/order-schema.js +954 -0
- package/dist/schemas/product/discount-relations.d.ts +15 -0
- package/dist/schemas/product/discount-relations.js +34 -0
- package/dist/schemas/product/discount-schema.d.ts +1975 -0
- package/dist/schemas/product/discount-schema.js +297 -0
- package/dist/schemas/product/product-relations.d.ts +41 -0
- package/dist/schemas/product/product-relations.js +133 -0
- package/dist/schemas/product/product-schema.d.ts +4544 -0
- package/dist/schemas/product/product-schema.js +671 -0
- package/dist/schemas/store/store-audit-schema.d.ts +4135 -0
- package/dist/schemas/store/store-audit-schema.js +556 -0
- package/dist/schemas/store/store-schema.d.ts +3100 -0
- package/dist/schemas/store/store-schema.js +381 -0
- package/dist/schemas/store/store-settings-schema.d.ts +665 -0
- package/dist/schemas/store/store-settings-schema.js +141 -0
- package/dist/schemas/types.d.ts +50 -0
- package/dist/schemas/types.js +3 -0
- package/dist/types/events.d.ts +2396 -0
- package/dist/types/events.js +505 -0
- package/dist/utils/errorHandler.d.ts +12 -0
- package/dist/utils/errorHandler.js +36 -0
- package/dist/utils/subdomain.d.ts +6 -0
- package/dist/utils/subdomain.js +20 -0
- package/nul +8 -0
- package/package.json +43 -0
- package/src/configs/index.ts +654 -0
- package/src/events/kafka.ts +429 -0
- package/src/index.ts +26 -0
- package/src/interfaces/customer-events.ts +106 -0
- package/src/interfaces/inventory-events.ts +545 -0
- package/src/interfaces/inventory-types.ts +1004 -0
- package/src/interfaces/order-events.ts +381 -0
- package/src/lib/auditLogger.ts +1117 -0
- package/src/lib/authOrganization.ts +153 -0
- package/src/lib/db.ts +64 -0
- package/src/middleware/serviceAuth.ts +328 -0
- package/src/middleware/storeOwnership.ts +199 -0
- package/src/middleware/storeValidationMiddleware.ts +247 -0
- package/src/middleware/userAuth.ts +248 -0
- package/src/schemas/admin/admin-schema.ts +208 -0
- package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -0
- package/src/schemas/common/common-schemas.ts +108 -0
- package/src/schemas/compliance/compliance-schema.ts +927 -0
- package/src/schemas/compliance/kyc-schema.ts +649 -0
- package/src/schemas/customer/customer-schema.ts +576 -0
- package/src/schemas/index.ts +189 -0
- package/src/schemas/inventory/inventory-tables.ts +1927 -0
- package/src/schemas/inventory/lot-tables.ts +799 -0
- package/src/schemas/order/order-schema.ts +1400 -0
- package/src/schemas/product/discount-relations.ts +44 -0
- package/src/schemas/product/discount-schema.ts +464 -0
- package/src/schemas/product/product-relations.ts +187 -0
- package/src/schemas/product/product-schema.ts +955 -0
- package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
- package/src/schemas/store/store-audit-schema.ts +1257 -0
- package/src/schemas/store/store-schema.ts +661 -0
- package/src/schemas/store/store-settings-schema.ts +231 -0
- package/src/schemas/types.ts +67 -0
- package/src/types/events.ts +646 -0
- package/src/utils/errorHandler.ts +44 -0
- package/src/utils/subdomain.ts +19 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { createId } from "@paralleldrive/cuid2";
|
|
2
|
+
import {
|
|
3
|
+
type Consumer,
|
|
4
|
+
type ConsumerConfig,
|
|
5
|
+
Kafka,
|
|
6
|
+
type KafkaConfig,
|
|
7
|
+
type KafkaMessage,
|
|
8
|
+
type Producer,
|
|
9
|
+
type ProducerConfig,
|
|
10
|
+
} from "kafkajs";
|
|
11
|
+
import {
|
|
12
|
+
type AxovaEvent,
|
|
13
|
+
KAFKA_TOPICS,
|
|
14
|
+
type KafkaTopic,
|
|
15
|
+
} from "../types/events";
|
|
16
|
+
|
|
17
|
+
// Kafka configuration interface
|
|
18
|
+
export interface AxovaKafkaConfig {
|
|
19
|
+
brokers: string[];
|
|
20
|
+
clientId: string;
|
|
21
|
+
ssl?: boolean;
|
|
22
|
+
sasl?:
|
|
23
|
+
| {
|
|
24
|
+
mechanism: "plain";
|
|
25
|
+
username: string;
|
|
26
|
+
password: string;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
mechanism: "scram-sha-256";
|
|
30
|
+
username: string;
|
|
31
|
+
password: string;
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
mechanism: "scram-sha-512";
|
|
35
|
+
username: string;
|
|
36
|
+
password: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Event handler type
|
|
41
|
+
export type EventHandler<T = unknown> = (event: T) => Promise<void>;
|
|
42
|
+
|
|
43
|
+
// Kafka client wrapper
|
|
44
|
+
export class AxovaKafka {
|
|
45
|
+
private kafka: Kafka;
|
|
46
|
+
private producer: Producer | null = null;
|
|
47
|
+
private consumers: Map<string, Consumer> = new Map();
|
|
48
|
+
private eventHandlers: Map<string, EventHandler[]> = new Map();
|
|
49
|
+
|
|
50
|
+
constructor(config: AxovaKafkaConfig) {
|
|
51
|
+
const kafkaConfig: KafkaConfig = {
|
|
52
|
+
clientId: config.clientId,
|
|
53
|
+
brokers: config.brokers,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (config.ssl) {
|
|
57
|
+
kafkaConfig.ssl = config.ssl;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (config.sasl) {
|
|
61
|
+
kafkaConfig.sasl = config.sasl;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.kafka = new Kafka(kafkaConfig);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Initialize producer with optimized settings for high throughput and reliability
|
|
68
|
+
async initializeProducer(config?: ProducerConfig): Promise<void> {
|
|
69
|
+
if (this.producer) return;
|
|
70
|
+
|
|
71
|
+
const optimizedConfig: ProducerConfig = {
|
|
72
|
+
// Idempotency ensures exactly-once semantics
|
|
73
|
+
idempotent: true,
|
|
74
|
+
|
|
75
|
+
// Allow up to 5 requests in flight for better throughput
|
|
76
|
+
// (safe with idempotent=true)
|
|
77
|
+
maxInFlightRequests: 5,
|
|
78
|
+
|
|
79
|
+
// Batching configuration for better throughput
|
|
80
|
+
allowAutoTopicCreation: false,
|
|
81
|
+
transactionTimeout: 60000,
|
|
82
|
+
|
|
83
|
+
// Retry configuration
|
|
84
|
+
retry: {
|
|
85
|
+
initialRetryTime: 100,
|
|
86
|
+
retries: 10,
|
|
87
|
+
maxRetryTime: 30000,
|
|
88
|
+
multiplier: 2,
|
|
89
|
+
factor: 0.2,
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Override with user config if provided
|
|
93
|
+
...config,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
this.producer = this.kafka.producer(optimizedConfig);
|
|
97
|
+
await this.producer.connect();
|
|
98
|
+
console.log("✅ Kafka producer connected with optimized settings");
|
|
99
|
+
console.log(" • Idempotent: enabled");
|
|
100
|
+
console.log(" • Max in-flight: 5");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Initialize consumer with optimized settings for throughput and reliability
|
|
104
|
+
async initializeConsumer(
|
|
105
|
+
groupId: string,
|
|
106
|
+
topics: KafkaTopic[],
|
|
107
|
+
config?: ConsumerConfig,
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
if (this.consumers.has(groupId)) return;
|
|
110
|
+
|
|
111
|
+
const optimizedConfig: ConsumerConfig = {
|
|
112
|
+
groupId,
|
|
113
|
+
|
|
114
|
+
// Session timeout - how long broker waits before considering consumer dead
|
|
115
|
+
sessionTimeout: 30000,
|
|
116
|
+
|
|
117
|
+
// Rebalance timeout - max time for rebalancing
|
|
118
|
+
rebalanceTimeout: 60000,
|
|
119
|
+
|
|
120
|
+
// Heartbeat interval - how often to send heartbeats
|
|
121
|
+
heartbeatInterval: 3000,
|
|
122
|
+
|
|
123
|
+
// Retry configuration
|
|
124
|
+
retry: {
|
|
125
|
+
initialRetryTime: 100,
|
|
126
|
+
retries: 10,
|
|
127
|
+
maxRetryTime: 30000,
|
|
128
|
+
multiplier: 2,
|
|
129
|
+
factor: 0.2,
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// Auto-commit configuration
|
|
133
|
+
// Note: For critical applications, consider manual commits
|
|
134
|
+
// autoCommit: false,
|
|
135
|
+
// autoCommitInterval: 5000,
|
|
136
|
+
|
|
137
|
+
// Performance tuning
|
|
138
|
+
maxBytesPerPartition: 1048576, // 1MB
|
|
139
|
+
maxWaitTimeInMs: 500, // Wait up to 500ms for batch
|
|
140
|
+
|
|
141
|
+
// Override with user config if provided
|
|
142
|
+
...config,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const consumer = this.kafka.consumer(optimizedConfig);
|
|
146
|
+
|
|
147
|
+
await consumer.connect();
|
|
148
|
+
await consumer.subscribe({
|
|
149
|
+
topics: topics,
|
|
150
|
+
fromBeginning: false,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
this.consumers.set(groupId, consumer);
|
|
154
|
+
console.log(`✅ Kafka consumer connected for group: ${groupId}`);
|
|
155
|
+
console.log(` • Topics: ${topics.join(", ")}`);
|
|
156
|
+
console.log(" • Session timeout: 30s");
|
|
157
|
+
console.log(" • Max bytes per partition: 1MB");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Publish event with optimized batching and error handling
|
|
161
|
+
async publishEvent(topic: KafkaTopic, event: AxovaEvent): Promise<void> {
|
|
162
|
+
if (!this.producer) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
"Producer not initialized. Call initializeProducer() first.",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Ensure all header values are strings (KafkaJS requirement)
|
|
169
|
+
const headers: Record<string, string> = {};
|
|
170
|
+
if (event.type) headers.eventType = String(event.type);
|
|
171
|
+
if (event.source) headers.source = String(event.source);
|
|
172
|
+
if (event.version) headers.version = String(event.version);
|
|
173
|
+
|
|
174
|
+
const message = {
|
|
175
|
+
key: event.id || createId(), // Use event ID or generate a new one as string key
|
|
176
|
+
value: JSON.stringify(event),
|
|
177
|
+
timestamp: Date.now().toString(),
|
|
178
|
+
headers,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const result = await this.producer.send({
|
|
183
|
+
topic,
|
|
184
|
+
messages: [message],
|
|
185
|
+
// Require acknowledgment from all in-sync replicas for durability
|
|
186
|
+
acks: -1, // Wait for all in-sync replicas
|
|
187
|
+
timeout: 30000,
|
|
188
|
+
compression: 1, // LZ4 compression
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
console.log(`✅ Event published successfully to ${topic}:`, event.type);
|
|
192
|
+
console.log(
|
|
193
|
+
` • Partition: ${result[0].partition}, Offset: ${result[0].baseOffset}`,
|
|
194
|
+
);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(`❌ Failed to publish event to ${topic}:`, error);
|
|
197
|
+
if (error instanceof Error) {
|
|
198
|
+
if (error.message.includes("UNKNOWN_TOPIC_OR_PARTITION")) {
|
|
199
|
+
console.error(
|
|
200
|
+
` • Topic '${topic}' does not exist. Create it manually or enable auto-creation.`,
|
|
201
|
+
);
|
|
202
|
+
} else if (error.message.includes("NOT_LEADER_FOR_PARTITION")) {
|
|
203
|
+
console.error(
|
|
204
|
+
" • Kafka cluster leadership issue. Check broker status.",
|
|
205
|
+
);
|
|
206
|
+
} else if (error.message.includes("NOT_ENOUGH_REPLICAS")) {
|
|
207
|
+
console.error(
|
|
208
|
+
" • Not enough in-sync replicas available. Check broker health.",
|
|
209
|
+
);
|
|
210
|
+
} else if (error.message.includes("REQUEST_TIMED_OUT")) {
|
|
211
|
+
console.error(
|
|
212
|
+
" • Request timed out. Check network and broker performance.",
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Batch publish events for higher throughput
|
|
221
|
+
async publishEventsBatch(topic: KafkaTopic, events: AxovaEvent[]): Promise<void> {
|
|
222
|
+
if (!this.producer) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"Producer not initialized. Call initializeProducer() first.",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (events.length === 0) return;
|
|
229
|
+
|
|
230
|
+
const messages = events.map((event) => {
|
|
231
|
+
const headers: Record<string, string> = {};
|
|
232
|
+
if (event.type) headers.eventType = String(event.type);
|
|
233
|
+
if (event.source) headers.source = String(event.source);
|
|
234
|
+
if (event.version) headers.version = String(event.version);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
key: event.id || createId(),
|
|
238
|
+
value: JSON.stringify(event),
|
|
239
|
+
timestamp: Date.now().toString(),
|
|
240
|
+
headers,
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const result = await this.producer.send({
|
|
246
|
+
topic,
|
|
247
|
+
messages,
|
|
248
|
+
acks: -1,
|
|
249
|
+
timeout: 30000,
|
|
250
|
+
compression: 1,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
console.log(`✅ Batch published ${events.length} events to ${topic}`);
|
|
254
|
+
console.log(` • Partitions used: ${result.length}`);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error(`❌ Failed to batch publish events to ${topic}:`, error);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Subscribe to events
|
|
262
|
+
onEvent<T extends AxovaEvent>(
|
|
263
|
+
eventType: T["type"],
|
|
264
|
+
handler: EventHandler<T>,
|
|
265
|
+
): void {
|
|
266
|
+
const handlers = this.eventHandlers.get(eventType) || [];
|
|
267
|
+
handlers.push(handler as EventHandler);
|
|
268
|
+
this.eventHandlers.set(eventType, handlers);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Start consuming events with optimized batch processing
|
|
272
|
+
async startConsuming(groupId: string): Promise<void> {
|
|
273
|
+
const consumer = this.consumers.get(groupId);
|
|
274
|
+
if (!consumer) {
|
|
275
|
+
throw new Error(`Consumer for group ${groupId} not initialized`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await consumer.run({
|
|
279
|
+
// Process messages in batches for better throughput
|
|
280
|
+
// Can be configured per service based on requirements
|
|
281
|
+
partitionsConsumedConcurrently: 3, // Process 3 partitions concurrently
|
|
282
|
+
|
|
283
|
+
eachMessage: async ({
|
|
284
|
+
topic,
|
|
285
|
+
partition,
|
|
286
|
+
message,
|
|
287
|
+
}: {
|
|
288
|
+
topic: string;
|
|
289
|
+
partition: number;
|
|
290
|
+
message: KafkaMessage;
|
|
291
|
+
}) => {
|
|
292
|
+
const startTime = Date.now();
|
|
293
|
+
try {
|
|
294
|
+
if (!message.value) return;
|
|
295
|
+
|
|
296
|
+
const event = JSON.parse(message.value.toString()) as AxovaEvent;
|
|
297
|
+
const handlers = this.eventHandlers.get(event.type) || [];
|
|
298
|
+
|
|
299
|
+
if (handlers.length === 0) {
|
|
300
|
+
console.warn(`⚠️ No handlers registered for event type: ${event.type}`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Execute all handlers for this event type
|
|
305
|
+
const handlerPromises = handlers.map(async (handler) => {
|
|
306
|
+
try {
|
|
307
|
+
await handler(event);
|
|
308
|
+
} catch (handlerError) {
|
|
309
|
+
console.error(
|
|
310
|
+
`❌ Handler error for event ${event.type}:`,
|
|
311
|
+
handlerError,
|
|
312
|
+
);
|
|
313
|
+
// Log error but don't throw - allows other handlers to execute
|
|
314
|
+
// TODO: Consider implementing dead letter queue for failed handlers
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Wait for all handlers to complete
|
|
319
|
+
await Promise.allSettled(handlerPromises);
|
|
320
|
+
|
|
321
|
+
const processingTime = Date.now() - startTime;
|
|
322
|
+
console.log(
|
|
323
|
+
`✅ Processed ${handlers.length} handler(s) for event: ${event.type} (${processingTime}ms)`,
|
|
324
|
+
);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
const processingTime = Date.now() - startTime;
|
|
327
|
+
console.error(
|
|
328
|
+
`❌ Failed to process message from ${topic} [partition ${partition}] (${processingTime}ms):`,
|
|
329
|
+
error,
|
|
330
|
+
);
|
|
331
|
+
// TODO: Implement dead letter queue for failed messages
|
|
332
|
+
// For now, the message will be retried based on consumer config
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
console.log(`✅ Started consuming events for group: ${groupId}`);
|
|
338
|
+
console.log(" • Processing 3 partitions concurrently");
|
|
339
|
+
console.log(" • Handlers execute in parallel per message");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Disconnect all connections
|
|
343
|
+
async disconnect(): Promise<void> {
|
|
344
|
+
if (this.producer) {
|
|
345
|
+
await this.producer.disconnect();
|
|
346
|
+
this.producer = null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
for (const [_groupId, consumer] of this.consumers.entries()) {
|
|
350
|
+
await consumer.disconnect();
|
|
351
|
+
}
|
|
352
|
+
this.consumers.clear();
|
|
353
|
+
|
|
354
|
+
console.log("All Kafka connections disconnected");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Utility method to create event with base properties
|
|
358
|
+
createEvent<T extends AxovaEvent>(
|
|
359
|
+
type: T["type"],
|
|
360
|
+
data: T["data"],
|
|
361
|
+
source: string,
|
|
362
|
+
): T {
|
|
363
|
+
// Validate required fields
|
|
364
|
+
if (!type) {
|
|
365
|
+
throw new Error("Event type is required for Kafka event creation");
|
|
366
|
+
}
|
|
367
|
+
if (!source) {
|
|
368
|
+
throw new Error("Event source is required for Kafka event creation");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
id: createId(),
|
|
373
|
+
timestamp: new Date().toISOString(),
|
|
374
|
+
source: String(source),
|
|
375
|
+
version: "1.0",
|
|
376
|
+
type: String(type),
|
|
377
|
+
data: data || {},
|
|
378
|
+
} as T;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Singleton factory for Kafka instance
|
|
383
|
+
let kafkaInstance: AxovaKafka | null = null;
|
|
384
|
+
|
|
385
|
+
export function createKafkaInstance(config: AxovaKafkaConfig): AxovaKafka {
|
|
386
|
+
if (!kafkaInstance) {
|
|
387
|
+
kafkaInstance = new AxovaKafka(config);
|
|
388
|
+
}
|
|
389
|
+
return kafkaInstance;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function getKafkaInstance(): AxovaKafka {
|
|
393
|
+
if (!kafkaInstance) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
"Kafka instance not created. Call createKafkaInstance() first.",
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return kafkaInstance;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Utility function to get Kafka config from environment
|
|
402
|
+
export function getKafkaConfigFromEnv(): AxovaKafkaConfig {
|
|
403
|
+
const brokers = process.env.KAFKA_BROKERS?.split(",") || ["localhost:9092"];
|
|
404
|
+
const clientId = process.env.KAFKA_CLIENT_ID || "axova-service";
|
|
405
|
+
|
|
406
|
+
const config: AxovaKafkaConfig = {
|
|
407
|
+
brokers,
|
|
408
|
+
clientId,
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// Add SSL if configured
|
|
412
|
+
if (process.env.KAFKA_SSL === "true") {
|
|
413
|
+
config.ssl = true;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Add SASL if configured
|
|
417
|
+
if (process.env.KAFKA_USERNAME && process.env.KAFKA_PASSWORD) {
|
|
418
|
+
config.sasl = {
|
|
419
|
+
mechanism: "plain",
|
|
420
|
+
username: process.env.KAFKA_USERNAME,
|
|
421
|
+
password: process.env.KAFKA_PASSWORD,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return config;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Export common topics for easy access
|
|
429
|
+
export { KAFKA_TOPICS };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Database
|
|
2
|
+
|
|
3
|
+
// Service Configuration Profiles
|
|
4
|
+
export * from "./configs";
|
|
5
|
+
// Kafka Events and Utilities
|
|
6
|
+
export * from "./events/kafka";
|
|
7
|
+
// Audit Logging
|
|
8
|
+
export * from "./lib/auditLogger";
|
|
9
|
+
// Auth Organization
|
|
10
|
+
export * from "./lib/authOrganization";
|
|
11
|
+
export { db, pool } from "./lib/db";
|
|
12
|
+
// Service Authentication
|
|
13
|
+
export * from "./middleware/serviceAuth";
|
|
14
|
+
export * from "./middleware/storeOwnership";
|
|
15
|
+
export * from "./middleware/storeValidationMiddleware";
|
|
16
|
+
export * from "./middleware/userAuth";
|
|
17
|
+
|
|
18
|
+
// Schemas - Organized by Service
|
|
19
|
+
export * from "./schemas";
|
|
20
|
+
|
|
21
|
+
// Types and Interfaces
|
|
22
|
+
export * from "./types/events";
|
|
23
|
+
|
|
24
|
+
// Utilities
|
|
25
|
+
export * from "./utils/errorHandler";
|
|
26
|
+
export * from "./utils/subdomain";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Customer Service Events Configuration
|
|
2
|
+
export interface CustomerServiceConfig {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
version: string;
|
|
5
|
+
events: CustomerServiceEvents;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CustomerServiceEvents {
|
|
9
|
+
CUSTOMER_CREATED: "customer.created";
|
|
10
|
+
CUSTOMER_UPDATED: "customer.updated";
|
|
11
|
+
CUSTOMER_DELETED: "customer.deleted";
|
|
12
|
+
CUSTOMER_VISIT_CREATED: "customer.visit.created";
|
|
13
|
+
CUSTOMER_INTERACTION_CREATED: "customer.interaction.created";
|
|
14
|
+
CUSTOMER_NOTE_CREATED: "customer.note.created";
|
|
15
|
+
CUSTOMER_SUPPORT_TICKET_CREATED: "customer.support.ticket.created";
|
|
16
|
+
CUSTOMER_REVIEW_SUBMITTED: "customer.review.submitted";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Customer Service Event Types
|
|
20
|
+
export type CustomerEventType =
|
|
21
|
+
| "customer.created"
|
|
22
|
+
| "customer.updated"
|
|
23
|
+
| "customer.deleted"
|
|
24
|
+
| "customer.visit.created"
|
|
25
|
+
| "customer.interaction.created"
|
|
26
|
+
| "customer.note.created"
|
|
27
|
+
| "customer.support.ticket.created"
|
|
28
|
+
| "customer.review.submitted";
|
|
29
|
+
|
|
30
|
+
// Customer Event Payloads
|
|
31
|
+
export interface CustomerCreatedEvent {
|
|
32
|
+
customerId: string;
|
|
33
|
+
userId: string;
|
|
34
|
+
email: string;
|
|
35
|
+
firstName: string;
|
|
36
|
+
lastName: string;
|
|
37
|
+
customerType: string;
|
|
38
|
+
registrationSource: string;
|
|
39
|
+
storeLocationId?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CustomerUpdatedEvent {
|
|
43
|
+
customerId: string;
|
|
44
|
+
userId: string;
|
|
45
|
+
changes: Record<string, unknown>;
|
|
46
|
+
previousValues: Record<string, unknown>;
|
|
47
|
+
registrationSourceChanged: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CustomerDeletedEvent {
|
|
51
|
+
customerId: string;
|
|
52
|
+
userId: string;
|
|
53
|
+
email: string;
|
|
54
|
+
customerType: string;
|
|
55
|
+
registrationSource: string;
|
|
56
|
+
totalSpent: string;
|
|
57
|
+
totalOrders: number;
|
|
58
|
+
totalVisits: number;
|
|
59
|
+
totalInteractions: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CustomerVisitCreatedEvent {
|
|
63
|
+
visitId: string;
|
|
64
|
+
customerId: string;
|
|
65
|
+
visitType: string;
|
|
66
|
+
storeLocationId?: string;
|
|
67
|
+
outcome?: string;
|
|
68
|
+
duration?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CustomerInteractionCreatedEvent {
|
|
72
|
+
interactionId: string;
|
|
73
|
+
customerId: string;
|
|
74
|
+
interactionType: string;
|
|
75
|
+
targetType?: string;
|
|
76
|
+
targetId?: string;
|
|
77
|
+
outcome?: string;
|
|
78
|
+
staffId?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CustomerNoteCreatedEvent {
|
|
82
|
+
noteId: string;
|
|
83
|
+
customerId: string;
|
|
84
|
+
noteType: string;
|
|
85
|
+
priority: string;
|
|
86
|
+
isAlert: boolean;
|
|
87
|
+
createdBy: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CustomerSupportTicketCreatedEvent {
|
|
91
|
+
ticketId: string;
|
|
92
|
+
ticketNumber: string;
|
|
93
|
+
customerId: string;
|
|
94
|
+
subject: string;
|
|
95
|
+
category: string;
|
|
96
|
+
priority: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface CustomerReviewSubmittedEvent {
|
|
100
|
+
reviewId: string;
|
|
101
|
+
customerId: string;
|
|
102
|
+
targetType: string;
|
|
103
|
+
targetId: string;
|
|
104
|
+
rating: number;
|
|
105
|
+
isVerifiedPurchase: boolean;
|
|
106
|
+
}
|