tlc-claude-code 1.2.29 → 1.4.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/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging Patterns Module
|
|
3
|
+
* Inter-service communication setup generator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate UUID v4
|
|
8
|
+
* @returns {string} UUID string
|
|
9
|
+
*/
|
|
10
|
+
function generateUuid() {
|
|
11
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
12
|
+
const r = (Math.random() * 16) | 0;
|
|
13
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
14
|
+
return v.toString(16);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* MessagingPatterns class for generating inter-service communication setup
|
|
20
|
+
*/
|
|
21
|
+
class MessagingPatterns {
|
|
22
|
+
/**
|
|
23
|
+
* Create a MessagingPatterns instance
|
|
24
|
+
* @param {Object} options - Configuration options
|
|
25
|
+
*/
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
this.options = options;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generate complete messaging setup
|
|
32
|
+
* @param {Object} config - Configuration
|
|
33
|
+
* @param {Array<string>} config.events - Event names
|
|
34
|
+
* @param {string} config.broker - Broker type (e.g., 'redis')
|
|
35
|
+
* @returns {Object} Generated files
|
|
36
|
+
*/
|
|
37
|
+
generate(config) {
|
|
38
|
+
const { events = [], broker = 'redis' } = config;
|
|
39
|
+
|
|
40
|
+
const files = [];
|
|
41
|
+
|
|
42
|
+
// Event bus config
|
|
43
|
+
files.push({
|
|
44
|
+
path: 'messaging/event-bus.js',
|
|
45
|
+
content: this.generateEventBusConfig(broker),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Publisher utility
|
|
49
|
+
files.push({
|
|
50
|
+
path: 'messaging/publisher.js',
|
|
51
|
+
content: this.generatePublisher(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Subscriber utility
|
|
55
|
+
files.push({
|
|
56
|
+
path: 'messaging/subscriber.js',
|
|
57
|
+
content: this.generateSubscriber(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Dead letter config
|
|
61
|
+
files.push({
|
|
62
|
+
path: 'messaging/dead-letter.js',
|
|
63
|
+
content: this.generateDeadLetterConfig(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Event catalog
|
|
67
|
+
files.push({
|
|
68
|
+
path: 'messaging/event-catalog.js',
|
|
69
|
+
content: this.generateEventCatalog(events),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Index file
|
|
73
|
+
files.push({
|
|
74
|
+
path: 'messaging/index.js',
|
|
75
|
+
content: this._generateIndex(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { files };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate event bus configuration
|
|
83
|
+
* @param {string} broker - Broker type
|
|
84
|
+
* @returns {string} Event bus configuration code
|
|
85
|
+
*/
|
|
86
|
+
generateEventBusConfig(broker) {
|
|
87
|
+
return `/**
|
|
88
|
+
* Event Bus Configuration
|
|
89
|
+
* Broker: ${broker}
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
const Redis = require('ioredis');
|
|
93
|
+
|
|
94
|
+
// Connection configuration
|
|
95
|
+
const config = {
|
|
96
|
+
broker: '${broker}',
|
|
97
|
+
connectionUrl: process.env.REDIS_URL || 'redis://localhost:6379',
|
|
98
|
+
options: {
|
|
99
|
+
// Retry configuration
|
|
100
|
+
retry: {
|
|
101
|
+
maxAttempts: 10,
|
|
102
|
+
initialDelay: 100,
|
|
103
|
+
maxDelay: 30000,
|
|
104
|
+
factor: 2,
|
|
105
|
+
},
|
|
106
|
+
// Connection options
|
|
107
|
+
lazyConnect: true,
|
|
108
|
+
enableReadyCheck: true,
|
|
109
|
+
maxRetriesPerRequest: 3,
|
|
110
|
+
},
|
|
111
|
+
// Channel naming convention
|
|
112
|
+
channel: {
|
|
113
|
+
prefix: 'events',
|
|
114
|
+
separator: ':',
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Create Redis client with retry logic
|
|
119
|
+
function createClient() {
|
|
120
|
+
const client = new Redis(config.connectionUrl, {
|
|
121
|
+
retryStrategy: (times) => {
|
|
122
|
+
if (times > config.options.retry.maxAttempts) {
|
|
123
|
+
return null; // Stop retrying
|
|
124
|
+
}
|
|
125
|
+
const delay = Math.min(
|
|
126
|
+
config.options.retry.initialDelay * Math.pow(config.options.retry.factor, times - 1),
|
|
127
|
+
config.options.retry.maxDelay
|
|
128
|
+
);
|
|
129
|
+
return delay;
|
|
130
|
+
},
|
|
131
|
+
lazyConnect: config.options.lazyConnect,
|
|
132
|
+
enableReadyCheck: config.options.enableReadyCheck,
|
|
133
|
+
maxRetriesPerRequest: config.options.maxRetriesPerRequest,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
client.on('error', (err) => {
|
|
137
|
+
console.error('[EventBus] Redis connection error:', err.message);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
client.on('connect', () => {
|
|
141
|
+
console.log('[EventBus] Connected to Redis');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
client.on('reconnecting', () => {
|
|
145
|
+
console.log('[EventBus] Reconnecting to Redis...');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return client;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Get channel name for an event
|
|
152
|
+
function getChannelName(eventName) {
|
|
153
|
+
return \`\${config.channel.prefix}\${config.channel.separator}\${eventName}\`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
config,
|
|
158
|
+
createClient,
|
|
159
|
+
getChannelName,
|
|
160
|
+
};
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate publisher utility code
|
|
166
|
+
* @returns {string} Publisher utility code
|
|
167
|
+
*/
|
|
168
|
+
generatePublisher() {
|
|
169
|
+
return `/**
|
|
170
|
+
* Message Publisher Utility
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
const { createClient, getChannelName } = require('./event-bus');
|
|
174
|
+
const { v4: uuidv4 } = require('uuid');
|
|
175
|
+
|
|
176
|
+
let publisherClient = null;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get or create publisher client
|
|
180
|
+
* @returns {Object} Redis client for publishing
|
|
181
|
+
*/
|
|
182
|
+
function getPublisherClient() {
|
|
183
|
+
if (!publisherClient) {
|
|
184
|
+
publisherClient = createClient();
|
|
185
|
+
}
|
|
186
|
+
return publisherClient;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Publish an event to the message broker
|
|
191
|
+
* @param {string} eventName - Name of the event
|
|
192
|
+
* @param {Object} payload - Event payload
|
|
193
|
+
* @param {Object} options - Additional options
|
|
194
|
+
* @returns {Promise<void>}
|
|
195
|
+
*/
|
|
196
|
+
async function publish(eventName, payload, options = {}) {
|
|
197
|
+
const client = getPublisherClient();
|
|
198
|
+
|
|
199
|
+
// Add metadata to message
|
|
200
|
+
const message = {
|
|
201
|
+
id: options.id || uuidv4(),
|
|
202
|
+
timestamp: options.timestamp || new Date().toISOString(),
|
|
203
|
+
source: options.source || process.env.SERVICE_NAME || 'unknown',
|
|
204
|
+
type: eventName,
|
|
205
|
+
payload,
|
|
206
|
+
metadata: options.metadata || {},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const channel = getChannelName(eventName);
|
|
211
|
+
const serialized = JSON.stringify(message);
|
|
212
|
+
|
|
213
|
+
await client.publish(channel, serialized);
|
|
214
|
+
|
|
215
|
+
console.log(\`[Publisher] Published \${eventName} to \${channel}\`);
|
|
216
|
+
|
|
217
|
+
return { success: true, messageId: message.id };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error(\`[Publisher] Failed to publish \${eventName}:\`, error.message);
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Publish multiple events in batch
|
|
226
|
+
* @param {Array<{eventName: string, payload: Object}>} events - Events to publish
|
|
227
|
+
* @returns {Promise<Array>}
|
|
228
|
+
*/
|
|
229
|
+
async function publishBatch(events) {
|
|
230
|
+
const results = [];
|
|
231
|
+
for (const event of events) {
|
|
232
|
+
try {
|
|
233
|
+
const result = await publish(event.eventName, event.payload, event.options);
|
|
234
|
+
results.push({ ...result, eventName: event.eventName });
|
|
235
|
+
} catch (error) {
|
|
236
|
+
results.push({ success: false, eventName: event.eventName, error: error.message });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return results;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Close publisher connection
|
|
244
|
+
* @returns {Promise<void>}
|
|
245
|
+
*/
|
|
246
|
+
async function closePublisher() {
|
|
247
|
+
if (publisherClient) {
|
|
248
|
+
await publisherClient.quit();
|
|
249
|
+
publisherClient = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = {
|
|
254
|
+
publish,
|
|
255
|
+
publishBatch,
|
|
256
|
+
closePublisher,
|
|
257
|
+
getPublisherClient,
|
|
258
|
+
};
|
|
259
|
+
`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Generate subscriber utility code
|
|
264
|
+
* @returns {string} Subscriber utility code
|
|
265
|
+
*/
|
|
266
|
+
generateSubscriber() {
|
|
267
|
+
return `/**
|
|
268
|
+
* Message Subscriber Utility
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
const { createClient, getChannelName } = require('./event-bus');
|
|
272
|
+
const { handleDeadLetter } = require('./dead-letter');
|
|
273
|
+
|
|
274
|
+
let subscriberClient = null;
|
|
275
|
+
const handlers = new Map();
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get or create subscriber client
|
|
279
|
+
* @returns {Object} Redis client for subscribing
|
|
280
|
+
*/
|
|
281
|
+
function getSubscriberClient() {
|
|
282
|
+
if (!subscriberClient) {
|
|
283
|
+
subscriberClient = createClient();
|
|
284
|
+
setupMessageHandler();
|
|
285
|
+
}
|
|
286
|
+
return subscriberClient;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Setup message handler for incoming messages
|
|
291
|
+
*/
|
|
292
|
+
function setupMessageHandler() {
|
|
293
|
+
subscriberClient.on('message', async (channel, rawMessage) => {
|
|
294
|
+
try {
|
|
295
|
+
// Deserialize message
|
|
296
|
+
const message = JSON.parse(rawMessage);
|
|
297
|
+
|
|
298
|
+
const eventHandlers = handlers.get(channel);
|
|
299
|
+
if (eventHandlers && eventHandlers.length > 0) {
|
|
300
|
+
for (const handler of eventHandlers) {
|
|
301
|
+
try {
|
|
302
|
+
await handler(message);
|
|
303
|
+
} catch (handlerError) {
|
|
304
|
+
console.error(\`[Subscriber] Handler error on \${channel}:\`, handlerError.message);
|
|
305
|
+
await handleDeadLetter(message, handlerError);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} catch (parseError) {
|
|
310
|
+
console.error('[Subscriber] Failed to parse message:', parseError.message);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Subscribe to an event
|
|
317
|
+
* @param {string} eventName - Name of the event
|
|
318
|
+
* @param {Function} handler - Handler function
|
|
319
|
+
* @returns {Promise<void>}
|
|
320
|
+
*/
|
|
321
|
+
async function subscribe(eventName, handler) {
|
|
322
|
+
const client = getSubscriberClient();
|
|
323
|
+
const channel = getChannelName(eventName);
|
|
324
|
+
|
|
325
|
+
// Register handler
|
|
326
|
+
if (!handlers.has(channel)) {
|
|
327
|
+
handlers.set(channel, []);
|
|
328
|
+
}
|
|
329
|
+
handlers.get(channel).push(handler);
|
|
330
|
+
|
|
331
|
+
// Subscribe to channel
|
|
332
|
+
await client.subscribe(channel);
|
|
333
|
+
|
|
334
|
+
console.log(\`[Subscriber] Subscribed to \${eventName} on \${channel}\`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Unsubscribe from an event
|
|
339
|
+
* @param {string} eventName - Name of the event
|
|
340
|
+
* @returns {Promise<void>}
|
|
341
|
+
*/
|
|
342
|
+
async function unsubscribe(eventName) {
|
|
343
|
+
const client = getSubscriberClient();
|
|
344
|
+
const channel = getChannelName(eventName);
|
|
345
|
+
|
|
346
|
+
await client.unsubscribe(channel);
|
|
347
|
+
handlers.delete(channel);
|
|
348
|
+
|
|
349
|
+
console.log(\`[Subscriber] Unsubscribed from \${eventName}\`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Close subscriber connection
|
|
354
|
+
* @returns {Promise<void>}
|
|
355
|
+
*/
|
|
356
|
+
async function closeSubscriber() {
|
|
357
|
+
if (subscriberClient) {
|
|
358
|
+
await subscriberClient.quit();
|
|
359
|
+
subscriberClient = null;
|
|
360
|
+
handlers.clear();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
module.exports = {
|
|
365
|
+
subscribe,
|
|
366
|
+
unsubscribe,
|
|
367
|
+
closeSubscriber,
|
|
368
|
+
getSubscriberClient,
|
|
369
|
+
};
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Generate dead letter queue configuration
|
|
375
|
+
* @returns {string} Dead letter queue configuration code
|
|
376
|
+
*/
|
|
377
|
+
generateDeadLetterConfig() {
|
|
378
|
+
return `/**
|
|
379
|
+
* Dead Letter Queue Configuration
|
|
380
|
+
* Handles failed message processing
|
|
381
|
+
*/
|
|
382
|
+
|
|
383
|
+
const { createClient, config } = require('./event-bus');
|
|
384
|
+
|
|
385
|
+
let deadLetterClient = null;
|
|
386
|
+
|
|
387
|
+
const DEAD_LETTER_PREFIX = 'dead:letter:queue';
|
|
388
|
+
const MAX_RETRY_COUNT = 3;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get or create dead letter client
|
|
392
|
+
* @returns {Object} Redis client for dead letter queue
|
|
393
|
+
*/
|
|
394
|
+
function getDeadLetterClient() {
|
|
395
|
+
if (!deadLetterClient) {
|
|
396
|
+
deadLetterClient = createClient();
|
|
397
|
+
}
|
|
398
|
+
return deadLetterClient;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Handle failed message by storing in dead letter queue
|
|
403
|
+
* @param {Object} message - Original message
|
|
404
|
+
* @param {Error} error - Error that occurred
|
|
405
|
+
* @returns {Promise<void>}
|
|
406
|
+
*/
|
|
407
|
+
async function handleDeadLetter(message, error) {
|
|
408
|
+
const client = getDeadLetterClient();
|
|
409
|
+
|
|
410
|
+
// Get current retry count
|
|
411
|
+
const retryCount = (message.metadata?.retryCount || 0) + 1;
|
|
412
|
+
|
|
413
|
+
const deadLetterEntry = {
|
|
414
|
+
originalMessage: message,
|
|
415
|
+
error: {
|
|
416
|
+
message: error.message,
|
|
417
|
+
stack: error.stack,
|
|
418
|
+
},
|
|
419
|
+
failedAt: new Date().toISOString(),
|
|
420
|
+
retryCount: retryCount,
|
|
421
|
+
maxRetries: MAX_RETRY_COUNT,
|
|
422
|
+
canRetry: retryCount < MAX_RETRY_COUNT,
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const key = \`\${DEAD_LETTER_PREFIX}:\${message.type}:\${message.id}\`;
|
|
426
|
+
|
|
427
|
+
await client.set(key, JSON.stringify(deadLetterEntry));
|
|
428
|
+
await client.lpush(\`\${DEAD_LETTER_PREFIX}:list\`, key);
|
|
429
|
+
|
|
430
|
+
console.log(\`[DeadLetter] Stored failed message \${message.id} (retry count: \${retryCount})\`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Get all messages in dead letter queue
|
|
435
|
+
* @param {Object} options - Query options
|
|
436
|
+
* @returns {Promise<Array>}
|
|
437
|
+
*/
|
|
438
|
+
async function getDeadLetterMessages(options = {}) {
|
|
439
|
+
const client = getDeadLetterClient();
|
|
440
|
+
const { limit = 100, offset = 0 } = options;
|
|
441
|
+
|
|
442
|
+
const keys = await client.lrange(\`\${DEAD_LETTER_PREFIX}:list\`, offset, offset + limit - 1);
|
|
443
|
+
|
|
444
|
+
const messages = [];
|
|
445
|
+
for (const key of keys) {
|
|
446
|
+
const data = await client.get(key);
|
|
447
|
+
if (data) {
|
|
448
|
+
messages.push(JSON.parse(data));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return messages;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Retry a dead letter message
|
|
457
|
+
* @param {string} messageId - Message ID to retry
|
|
458
|
+
* @param {string} eventType - Event type
|
|
459
|
+
* @returns {Promise<Object>}
|
|
460
|
+
*/
|
|
461
|
+
async function retryDeadLetter(messageId, eventType) {
|
|
462
|
+
const client = getDeadLetterClient();
|
|
463
|
+
const key = \`\${DEAD_LETTER_PREFIX}:\${eventType}:\${messageId}\`;
|
|
464
|
+
|
|
465
|
+
const data = await client.get(key);
|
|
466
|
+
if (!data) {
|
|
467
|
+
throw new Error(\`Dead letter message not found: \${messageId}\`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const entry = JSON.parse(data);
|
|
471
|
+
|
|
472
|
+
if (!entry.canRetry) {
|
|
473
|
+
throw new Error(\`Message \${messageId} has exceeded max retry count\`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Update retry count in original message
|
|
477
|
+
const messageToRetry = {
|
|
478
|
+
...entry.originalMessage,
|
|
479
|
+
metadata: {
|
|
480
|
+
...entry.originalMessage.metadata,
|
|
481
|
+
retryCount: entry.retryCount,
|
|
482
|
+
retriedAt: new Date().toISOString(),
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// Remove from dead letter queue
|
|
487
|
+
await client.del(key);
|
|
488
|
+
await client.lrem(\`\${DEAD_LETTER_PREFIX}:list\`, 1, key);
|
|
489
|
+
|
|
490
|
+
return messageToRetry;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Purge dead letter queue
|
|
495
|
+
* @param {Object} options - Purge options
|
|
496
|
+
* @returns {Promise<number>} Number of messages purged
|
|
497
|
+
*/
|
|
498
|
+
async function purgeDeadLetter(options = {}) {
|
|
499
|
+
const client = getDeadLetterClient();
|
|
500
|
+
const { olderThan } = options;
|
|
501
|
+
|
|
502
|
+
const keys = await client.lrange(\`\${DEAD_LETTER_PREFIX}:list\`, 0, -1);
|
|
503
|
+
|
|
504
|
+
let purged = 0;
|
|
505
|
+
for (const key of keys) {
|
|
506
|
+
const data = await client.get(key);
|
|
507
|
+
if (data) {
|
|
508
|
+
const entry = JSON.parse(data);
|
|
509
|
+
const shouldPurge = !olderThan || new Date(entry.failedAt) < olderThan;
|
|
510
|
+
|
|
511
|
+
if (shouldPurge) {
|
|
512
|
+
await client.del(key);
|
|
513
|
+
await client.lrem(\`\${DEAD_LETTER_PREFIX}:list\`, 1, key);
|
|
514
|
+
purged++;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return purged;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Close dead letter client
|
|
524
|
+
* @returns {Promise<void>}
|
|
525
|
+
*/
|
|
526
|
+
async function closeDeadLetterClient() {
|
|
527
|
+
if (deadLetterClient) {
|
|
528
|
+
await deadLetterClient.quit();
|
|
529
|
+
deadLetterClient = null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
module.exports = {
|
|
534
|
+
handleDeadLetter,
|
|
535
|
+
getDeadLetterMessages,
|
|
536
|
+
retryDeadLetter,
|
|
537
|
+
purgeDeadLetter,
|
|
538
|
+
closeDeadLetterClient,
|
|
539
|
+
MAX_RETRY_COUNT,
|
|
540
|
+
DEAD_LETTER_PREFIX,
|
|
541
|
+
};
|
|
542
|
+
`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Generate event catalog
|
|
547
|
+
* @param {Array<string>} events - Event names
|
|
548
|
+
* @returns {string} Event catalog code
|
|
549
|
+
*/
|
|
550
|
+
generateEventCatalog(events) {
|
|
551
|
+
const eventEntries = events
|
|
552
|
+
.map((event) => {
|
|
553
|
+
const schemaExample = this._generateEventSchema(event);
|
|
554
|
+
return ` ${event}: {
|
|
555
|
+
name: '${event}',
|
|
556
|
+
description: '${this._generateEventDescription(event)}',
|
|
557
|
+
schema: ${JSON.stringify(schemaExample.schema, null, 6).split('\n').join('\n ')},
|
|
558
|
+
example: ${JSON.stringify(schemaExample.example, null, 6).split('\n').join('\n ')},
|
|
559
|
+
}`;
|
|
560
|
+
})
|
|
561
|
+
.join(',\n');
|
|
562
|
+
|
|
563
|
+
return `/**
|
|
564
|
+
* Event Catalog
|
|
565
|
+
* Lists all event types with schemas and examples
|
|
566
|
+
*/
|
|
567
|
+
|
|
568
|
+
const catalog = {
|
|
569
|
+
${eventEntries}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Get all event types
|
|
574
|
+
* @returns {Array<string>}
|
|
575
|
+
*/
|
|
576
|
+
function getEventTypes() {
|
|
577
|
+
return Object.keys(catalog);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Get event schema
|
|
582
|
+
* @param {string} eventName - Event name
|
|
583
|
+
* @returns {Object|null}
|
|
584
|
+
*/
|
|
585
|
+
function getEventSchema(eventName) {
|
|
586
|
+
const event = catalog[eventName];
|
|
587
|
+
return event ? event.schema : null;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Get event example
|
|
592
|
+
* @param {string} eventName - Event name
|
|
593
|
+
* @returns {Object|null}
|
|
594
|
+
*/
|
|
595
|
+
function getEventExample(eventName) {
|
|
596
|
+
const event = catalog[eventName];
|
|
597
|
+
return event ? event.example : null;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Validate event payload against schema
|
|
602
|
+
* @param {string} eventName - Event name
|
|
603
|
+
* @param {Object} payload - Payload to validate
|
|
604
|
+
* @returns {Object} Validation result
|
|
605
|
+
*/
|
|
606
|
+
function validateEventPayload(eventName, payload) {
|
|
607
|
+
const schema = getEventSchema(eventName);
|
|
608
|
+
if (!schema) {
|
|
609
|
+
return { valid: false, errors: ['Unknown event type'] };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const errors = [];
|
|
613
|
+
const required = schema.required || [];
|
|
614
|
+
|
|
615
|
+
for (const field of required) {
|
|
616
|
+
if (payload[field] === undefined) {
|
|
617
|
+
errors.push(\`Missing required field: \${field}\`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return {
|
|
622
|
+
valid: errors.length === 0,
|
|
623
|
+
errors,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
module.exports = {
|
|
628
|
+
catalog,
|
|
629
|
+
getEventTypes,
|
|
630
|
+
getEventSchema,
|
|
631
|
+
getEventExample,
|
|
632
|
+
validateEventPayload,
|
|
633
|
+
};
|
|
634
|
+
`;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Generate event schema based on event name
|
|
639
|
+
* @private
|
|
640
|
+
*/
|
|
641
|
+
_generateEventSchema(eventName) {
|
|
642
|
+
// Parse event name to determine schema
|
|
643
|
+
const parts = eventName.match(/([A-Z][a-z]+)/g) || [eventName];
|
|
644
|
+
const entity = parts[0].toLowerCase();
|
|
645
|
+
const action = parts.slice(1).join('').toLowerCase() || 'action';
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
schema: {
|
|
649
|
+
type: 'object',
|
|
650
|
+
required: ['id', 'timestamp', 'type', 'payload'],
|
|
651
|
+
properties: {
|
|
652
|
+
id: { type: 'string', format: 'uuid' },
|
|
653
|
+
timestamp: { type: 'string', format: 'date-time' },
|
|
654
|
+
type: { type: 'string', const: eventName },
|
|
655
|
+
source: { type: 'string' },
|
|
656
|
+
payload: {
|
|
657
|
+
type: 'object',
|
|
658
|
+
properties: {
|
|
659
|
+
[`${entity}Id`]: { type: 'string' },
|
|
660
|
+
data: { type: 'object' },
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
metadata: { type: 'object' },
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
example: {
|
|
667
|
+
id: 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d',
|
|
668
|
+
timestamp: '2024-01-15T10:30:00.000Z',
|
|
669
|
+
type: eventName,
|
|
670
|
+
source: 'service-name',
|
|
671
|
+
payload: {
|
|
672
|
+
[`${entity}Id`]: 'entity-123',
|
|
673
|
+
data: {},
|
|
674
|
+
},
|
|
675
|
+
metadata: {},
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Generate event description based on name
|
|
682
|
+
* @private
|
|
683
|
+
*/
|
|
684
|
+
_generateEventDescription(eventName) {
|
|
685
|
+
const parts = eventName.match(/([A-Z][a-z]+)/g) || [eventName];
|
|
686
|
+
if (parts.length >= 2) {
|
|
687
|
+
return `Emitted when a ${parts[0].toLowerCase()} is ${parts.slice(1).join(' ').toLowerCase()}`;
|
|
688
|
+
}
|
|
689
|
+
return `Event: ${eventName}`;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Generate index file
|
|
694
|
+
* @private
|
|
695
|
+
*/
|
|
696
|
+
_generateIndex() {
|
|
697
|
+
return `/**
|
|
698
|
+
* Messaging Module Index
|
|
699
|
+
*/
|
|
700
|
+
|
|
701
|
+
const eventBus = require('./event-bus');
|
|
702
|
+
const publisher = require('./publisher');
|
|
703
|
+
const subscriber = require('./subscriber');
|
|
704
|
+
const deadLetter = require('./dead-letter');
|
|
705
|
+
const eventCatalog = require('./event-catalog');
|
|
706
|
+
|
|
707
|
+
module.exports = {
|
|
708
|
+
// Event Bus
|
|
709
|
+
...eventBus,
|
|
710
|
+
|
|
711
|
+
// Publisher
|
|
712
|
+
publish: publisher.publish,
|
|
713
|
+
publishBatch: publisher.publishBatch,
|
|
714
|
+
closePublisher: publisher.closePublisher,
|
|
715
|
+
|
|
716
|
+
// Subscriber
|
|
717
|
+
subscribe: subscriber.subscribe,
|
|
718
|
+
unsubscribe: subscriber.unsubscribe,
|
|
719
|
+
closeSubscriber: subscriber.closeSubscriber,
|
|
720
|
+
|
|
721
|
+
// Dead Letter
|
|
722
|
+
handleDeadLetter: deadLetter.handleDeadLetter,
|
|
723
|
+
getDeadLetterMessages: deadLetter.getDeadLetterMessages,
|
|
724
|
+
retryDeadLetter: deadLetter.retryDeadLetter,
|
|
725
|
+
purgeDeadLetter: deadLetter.purgeDeadLetter,
|
|
726
|
+
|
|
727
|
+
// Event Catalog
|
|
728
|
+
catalog: eventCatalog.catalog,
|
|
729
|
+
getEventTypes: eventCatalog.getEventTypes,
|
|
730
|
+
getEventSchema: eventCatalog.getEventSchema,
|
|
731
|
+
getEventExample: eventCatalog.getEventExample,
|
|
732
|
+
validateEventPayload: eventCatalog.validateEventPayload,
|
|
733
|
+
};
|
|
734
|
+
`;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Create messaging patterns instance
|
|
740
|
+
* @param {Object} options - Options
|
|
741
|
+
* @returns {MessagingPatterns} Instance
|
|
742
|
+
*/
|
|
743
|
+
function createMessagingPatterns(options = {}) {
|
|
744
|
+
return new MessagingPatterns(options);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
module.exports = {
|
|
748
|
+
MessagingPatterns,
|
|
749
|
+
createMessagingPatterns,
|
|
750
|
+
};
|