@parsrun/service 0.1.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.
@@ -0,0 +1,853 @@
1
+ // src/events/format.ts
2
+ import { generateId } from "@parsrun/core";
3
+ function createEvent(options) {
4
+ const event = {
5
+ specversion: "1.0",
6
+ type: options.type,
7
+ source: options.source,
8
+ id: options.id ?? generateId(),
9
+ time: (/* @__PURE__ */ new Date()).toISOString(),
10
+ datacontenttype: "application/json",
11
+ data: options.data
12
+ };
13
+ if (options.subject) event.subject = options.subject;
14
+ if (options.tenantId) event.parstenantid = options.tenantId;
15
+ if (options.requestId) event.parsrequestid = options.requestId;
16
+ if (options.traceContext) event.parstracecontext = formatTraceContext(options.traceContext);
17
+ if (options.delivery) event.parsdelivery = options.delivery;
18
+ return event;
19
+ }
20
+ function toCloudEvent(event) {
21
+ return { ...event };
22
+ }
23
+ function toCompactEvent(event) {
24
+ const compact = {
25
+ e: event.type,
26
+ s: event.source,
27
+ i: event.id,
28
+ t: new Date(event.time).getTime(),
29
+ d: event.data
30
+ };
31
+ if (event.parstracecontext) compact.ctx = event.parstracecontext;
32
+ if (event.parstenantid) compact.tid = event.parstenantid;
33
+ return compact;
34
+ }
35
+ function fromCompactEvent(compact, source) {
36
+ const event = {
37
+ specversion: "1.0",
38
+ type: compact.e,
39
+ source: source ?? compact.s,
40
+ id: compact.i,
41
+ time: new Date(compact.t).toISOString(),
42
+ datacontenttype: "application/json",
43
+ data: compact.d
44
+ };
45
+ if (compact.ctx) event.parstracecontext = compact.ctx;
46
+ if (compact.tid) event.parstenantid = compact.tid;
47
+ return event;
48
+ }
49
+ function formatEventType(source, type) {
50
+ return `com.pars.${source}.${type}`;
51
+ }
52
+ function parseEventType(fullType) {
53
+ const prefix = "com.pars.";
54
+ if (!fullType.startsWith(prefix)) {
55
+ const parts = fullType.split(".");
56
+ if (parts.length >= 2) {
57
+ const [source, ...rest] = parts;
58
+ return { source, type: rest.join(".") };
59
+ }
60
+ return null;
61
+ }
62
+ const withoutPrefix = fullType.slice(prefix.length);
63
+ const dotIndex = withoutPrefix.indexOf(".");
64
+ if (dotIndex === -1) {
65
+ return null;
66
+ }
67
+ return {
68
+ source: withoutPrefix.slice(0, dotIndex),
69
+ type: withoutPrefix.slice(dotIndex + 1)
70
+ };
71
+ }
72
+ function matchEventType(type, pattern) {
73
+ if (pattern === "*" || pattern === "**") {
74
+ return true;
75
+ }
76
+ const typeParts = type.split(".");
77
+ const patternParts = pattern.split(".");
78
+ let ti = 0;
79
+ let pi = 0;
80
+ while (ti < typeParts.length && pi < patternParts.length) {
81
+ const pp = patternParts[pi];
82
+ if (pp === "**") {
83
+ if (pi === patternParts.length - 1) {
84
+ return true;
85
+ }
86
+ for (let i = ti; i <= typeParts.length; i++) {
87
+ const remaining = typeParts.slice(i).join(".");
88
+ const remainingPattern = patternParts.slice(pi + 1).join(".");
89
+ if (matchEventType(remaining, remainingPattern)) {
90
+ return true;
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+ if (pp === "*") {
96
+ ti++;
97
+ pi++;
98
+ continue;
99
+ }
100
+ if (pp !== typeParts[ti]) {
101
+ return false;
102
+ }
103
+ ti++;
104
+ pi++;
105
+ }
106
+ return ti === typeParts.length && pi === patternParts.length;
107
+ }
108
+ function formatTraceContext(ctx) {
109
+ const flags = ctx.traceFlags.toString(16).padStart(2, "0");
110
+ return `00-${ctx.traceId}-${ctx.spanId}-${flags}`;
111
+ }
112
+ function validateEvent(event) {
113
+ if (!event || typeof event !== "object") {
114
+ return false;
115
+ }
116
+ const e = event;
117
+ if (e["specversion"] !== "1.0") return false;
118
+ if (typeof e["type"] !== "string" || e["type"].length === 0) return false;
119
+ if (typeof e["source"] !== "string" || e["source"].length === 0) return false;
120
+ if (typeof e["id"] !== "string" || e["id"].length === 0) return false;
121
+ if (typeof e["time"] !== "string") return false;
122
+ return true;
123
+ }
124
+ function validateCompactEvent(event) {
125
+ if (!event || typeof event !== "object") {
126
+ return false;
127
+ }
128
+ const e = event;
129
+ if (typeof e["e"] !== "string" || e["e"].length === 0) return false;
130
+ if (typeof e["s"] !== "string" || e["s"].length === 0) return false;
131
+ if (typeof e["i"] !== "string" || e["i"].length === 0) return false;
132
+ if (typeof e["t"] !== "number") return false;
133
+ return true;
134
+ }
135
+
136
+ // src/events/emitter.ts
137
+ import { createLogger } from "@parsrun/core";
138
+ var EventEmitter = class {
139
+ service;
140
+ definition;
141
+ transport;
142
+ logger;
143
+ defaultTenantId;
144
+ validateEvents;
145
+ constructor(options) {
146
+ this.service = options.service;
147
+ if (options.definition) {
148
+ this.definition = options.definition;
149
+ }
150
+ this.transport = options.transport;
151
+ this.logger = options.logger ?? createLogger({ name: `events:${options.service}` });
152
+ if (options.defaultTenantId) {
153
+ this.defaultTenantId = options.defaultTenantId;
154
+ }
155
+ this.validateEvents = options.validateEvents ?? true;
156
+ }
157
+ /**
158
+ * Emit an event
159
+ */
160
+ async emit(type, data, options) {
161
+ if (this.validateEvents && this.definition?.events?.emits) {
162
+ const emits = this.definition.events.emits;
163
+ if (!(type in emits)) {
164
+ this.logger.warn(`Event type not declared in service definition: ${type}`);
165
+ }
166
+ }
167
+ let delivery;
168
+ if (this.definition?.events?.emits?.[type]) {
169
+ delivery = this.definition.events.emits[type].delivery;
170
+ }
171
+ const eventOptions = {
172
+ type,
173
+ source: this.service,
174
+ data
175
+ };
176
+ if (options?.eventId) eventOptions.id = options.eventId;
177
+ if (options?.subject) eventOptions.subject = options.subject;
178
+ const tenantId = options?.tenantId ?? this.defaultTenantId;
179
+ if (tenantId) eventOptions.tenantId = tenantId;
180
+ if (options?.requestId) eventOptions.requestId = options.requestId;
181
+ if (options?.traceContext) eventOptions.traceContext = options.traceContext;
182
+ const eventDelivery = options?.delivery ?? delivery;
183
+ if (eventDelivery) eventOptions.delivery = eventDelivery;
184
+ const event = createEvent(eventOptions);
185
+ try {
186
+ await this.transport.emit(event);
187
+ this.logger.debug(`Event emitted: ${type}`, {
188
+ eventId: event.id,
189
+ tenantId: event.parstenantid
190
+ });
191
+ return event.id;
192
+ } catch (error) {
193
+ this.logger.error(`Failed to emit event: ${type}`, error, {
194
+ eventId: event.id
195
+ });
196
+ throw error;
197
+ }
198
+ }
199
+ /**
200
+ * Emit multiple events
201
+ */
202
+ async emitBatch(events) {
203
+ const results = [];
204
+ for (const { type, data, options } of events) {
205
+ const eventId = await this.emit(type, data, options);
206
+ results.push(eventId);
207
+ }
208
+ return results;
209
+ }
210
+ /**
211
+ * Create a scoped emitter with preset options
212
+ */
213
+ scoped(options) {
214
+ return new ScopedEmitter(this, options);
215
+ }
216
+ /**
217
+ * Get service name
218
+ */
219
+ get serviceName() {
220
+ return this.service;
221
+ }
222
+ };
223
+ var ScopedEmitter = class {
224
+ emitter;
225
+ defaultOptions;
226
+ constructor(emitter, defaultOptions) {
227
+ this.emitter = emitter;
228
+ this.defaultOptions = defaultOptions;
229
+ }
230
+ async emit(type, data, options) {
231
+ return this.emitter.emit(type, data, {
232
+ ...this.defaultOptions,
233
+ ...options
234
+ });
235
+ }
236
+ };
237
+ function createEventEmitter(options) {
238
+ return new EventEmitter(options);
239
+ }
240
+ function createTypedEmitter(definition, options) {
241
+ const emitter = new EventEmitter({
242
+ ...options,
243
+ service: definition.name,
244
+ definition
245
+ });
246
+ return emitter;
247
+ }
248
+
249
+ // src/events/handler.ts
250
+ import { createLogger as createLogger2 } from "@parsrun/core";
251
+ var EventHandlerRegistry = class {
252
+ handlers = /* @__PURE__ */ new Map();
253
+ logger;
254
+ deadLetterQueue;
255
+ defaultOptions;
256
+ constructor(options = {}) {
257
+ this.logger = options.logger ?? createLogger2({ name: "event-handler" });
258
+ if (options.deadLetterQueue) {
259
+ this.deadLetterQueue = options.deadLetterQueue;
260
+ }
261
+ const defaultOpts = {
262
+ retries: options.defaultOptions?.retries ?? 3,
263
+ backoff: options.defaultOptions?.backoff ?? "exponential",
264
+ maxDelay: options.defaultOptions?.maxDelay ?? 3e4,
265
+ onExhausted: options.defaultOptions?.onExhausted ?? "log"
266
+ };
267
+ if (options.defaultOptions?.deadLetter) {
268
+ defaultOpts.deadLetter = options.defaultOptions.deadLetter;
269
+ }
270
+ this.defaultOptions = defaultOpts;
271
+ }
272
+ /**
273
+ * Register an event handler
274
+ */
275
+ register(pattern, handler, options) {
276
+ const registration = {
277
+ pattern,
278
+ handler,
279
+ options: {
280
+ ...this.defaultOptions,
281
+ ...options
282
+ }
283
+ };
284
+ const handlers = this.handlers.get(pattern) ?? [];
285
+ handlers.push(registration);
286
+ this.handlers.set(pattern, handlers);
287
+ this.logger.debug(`Handler registered for pattern: ${pattern}`);
288
+ return () => {
289
+ const currentHandlers = this.handlers.get(pattern);
290
+ if (currentHandlers) {
291
+ const index = currentHandlers.indexOf(registration);
292
+ if (index !== -1) {
293
+ currentHandlers.splice(index, 1);
294
+ if (currentHandlers.length === 0) {
295
+ this.handlers.delete(pattern);
296
+ }
297
+ this.logger.debug(`Handler unregistered for pattern: ${pattern}`);
298
+ }
299
+ }
300
+ };
301
+ }
302
+ /**
303
+ * Handle an event
304
+ */
305
+ async handle(event) {
306
+ const matchingHandlers = this.getMatchingHandlers(event.type);
307
+ if (matchingHandlers.length === 0) {
308
+ this.logger.debug(`No handlers for event type: ${event.type}`, {
309
+ eventId: event.id
310
+ });
311
+ return;
312
+ }
313
+ this.logger.debug(`Handling event: ${event.type}`, {
314
+ eventId: event.id,
315
+ handlerCount: matchingHandlers.length
316
+ });
317
+ const results = await Promise.allSettled(
318
+ matchingHandlers.map((reg) => this.executeHandler(event, reg))
319
+ );
320
+ for (let i = 0; i < results.length; i++) {
321
+ const result = results[i];
322
+ if (result?.status === "rejected") {
323
+ this.logger.error(
324
+ `Handler failed for ${event.type}`,
325
+ result.reason,
326
+ { eventId: event.id, pattern: matchingHandlers[i]?.pattern }
327
+ );
328
+ }
329
+ }
330
+ }
331
+ /**
332
+ * Execute a single handler with retry logic
333
+ */
334
+ async executeHandler(event, registration) {
335
+ const { handler, options } = registration;
336
+ const maxAttempts = options.retries + 1;
337
+ let lastError;
338
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
339
+ try {
340
+ const context = {
341
+ logger: this.logger.child({
342
+ eventId: event.id,
343
+ pattern: registration.pattern,
344
+ attempt
345
+ }),
346
+ attempt,
347
+ maxAttempts,
348
+ isRetry: attempt > 1
349
+ };
350
+ if (event.parstracecontext) {
351
+ const traceCtx = parseTraceContext(event.parstracecontext);
352
+ if (traceCtx) {
353
+ context.traceContext = traceCtx;
354
+ }
355
+ }
356
+ await handler(event, context);
357
+ return;
358
+ } catch (error) {
359
+ lastError = error;
360
+ if (attempt < maxAttempts) {
361
+ const delay = this.calculateBackoff(attempt, options);
362
+ this.logger.warn(
363
+ `Handler failed, retrying in ${delay}ms`,
364
+ { eventId: event.id, attempt, maxAttempts }
365
+ );
366
+ await sleep(delay);
367
+ }
368
+ }
369
+ }
370
+ await this.handleExhausted(event, registration, lastError);
371
+ }
372
+ /**
373
+ * Calculate backoff delay
374
+ */
375
+ calculateBackoff(attempt, options) {
376
+ const baseDelay = 100;
377
+ if (options.backoff === "exponential") {
378
+ return Math.min(baseDelay * Math.pow(2, attempt - 1), options.maxDelay);
379
+ }
380
+ return Math.min(baseDelay * attempt, options.maxDelay);
381
+ }
382
+ /**
383
+ * Handle exhausted retries
384
+ */
385
+ async handleExhausted(event, registration, error) {
386
+ const { options } = registration;
387
+ if (options.deadLetter && this.deadLetterQueue) {
388
+ await this.deadLetterQueue.add({
389
+ event,
390
+ error: error.message,
391
+ pattern: registration.pattern,
392
+ attempts: options.retries + 1
393
+ });
394
+ }
395
+ switch (options.onExhausted) {
396
+ case "alert":
397
+ this.logger.error(
398
+ `[ALERT] Event handler exhausted all retries`,
399
+ error,
400
+ {
401
+ eventId: event.id,
402
+ eventType: event.type,
403
+ pattern: registration.pattern
404
+ }
405
+ );
406
+ break;
407
+ case "discard":
408
+ this.logger.debug(`Event discarded after exhausted retries`, {
409
+ eventId: event.id
410
+ });
411
+ break;
412
+ case "log":
413
+ default:
414
+ this.logger.warn(`Event handler exhausted all retries`, {
415
+ eventId: event.id,
416
+ error: error.message
417
+ });
418
+ }
419
+ }
420
+ /**
421
+ * Get handlers matching an event type
422
+ */
423
+ getMatchingHandlers(eventType) {
424
+ const matching = [];
425
+ for (const [pattern, handlers] of this.handlers) {
426
+ if (matchEventType(eventType, pattern)) {
427
+ matching.push(...handlers);
428
+ }
429
+ }
430
+ return matching;
431
+ }
432
+ /**
433
+ * Get all registered patterns
434
+ */
435
+ getPatterns() {
436
+ return Array.from(this.handlers.keys());
437
+ }
438
+ /**
439
+ * Check if a pattern has handlers
440
+ */
441
+ hasHandlers(pattern) {
442
+ return this.handlers.has(pattern);
443
+ }
444
+ /**
445
+ * Clear all handlers
446
+ */
447
+ clear() {
448
+ this.handlers.clear();
449
+ }
450
+ };
451
+ function createEventHandlerRegistry(options) {
452
+ return new EventHandlerRegistry(options);
453
+ }
454
+ function sleep(ms) {
455
+ return new Promise((resolve) => setTimeout(resolve, ms));
456
+ }
457
+ function parseTraceContext(traceparent) {
458
+ const parts = traceparent.split("-");
459
+ if (parts.length !== 4) return void 0;
460
+ const [, traceId, spanId, flags] = parts;
461
+ if (!traceId || !spanId || !flags) return void 0;
462
+ return {
463
+ traceId,
464
+ spanId,
465
+ traceFlags: parseInt(flags, 16)
466
+ };
467
+ }
468
+
469
+ // src/events/transports/memory.ts
470
+ import { createLogger as createLogger3 } from "@parsrun/core";
471
+ var MemoryEventTransport = class {
472
+ name = "memory";
473
+ registry;
474
+ logger;
475
+ sync;
476
+ pendingEvents = [];
477
+ processing = false;
478
+ constructor(options = {}) {
479
+ this.logger = options.logger ?? createLogger3({ name: "memory-transport" });
480
+ this.sync = options.sync ?? false;
481
+ const registryOptions = {
482
+ logger: this.logger
483
+ };
484
+ if (options.defaultHandlerOptions) {
485
+ registryOptions.defaultOptions = options.defaultHandlerOptions;
486
+ }
487
+ this.registry = new EventHandlerRegistry(registryOptions);
488
+ }
489
+ /**
490
+ * Emit an event
491
+ */
492
+ async emit(event) {
493
+ this.logger.debug(`Event emitted: ${event.type}`, {
494
+ eventId: event.id,
495
+ tenantId: event.parstenantid
496
+ });
497
+ if (this.sync) {
498
+ await this.registry.handle(event);
499
+ } else {
500
+ this.pendingEvents.push(event);
501
+ this.processQueue();
502
+ }
503
+ }
504
+ /**
505
+ * Subscribe to events
506
+ */
507
+ subscribe(eventType, handler, options) {
508
+ return this.registry.register(eventType, handler, options);
509
+ }
510
+ /**
511
+ * Process pending events asynchronously
512
+ */
513
+ async processQueue() {
514
+ if (this.processing) return;
515
+ this.processing = true;
516
+ try {
517
+ while (this.pendingEvents.length > 0) {
518
+ const event = this.pendingEvents.shift();
519
+ if (event) {
520
+ await this.registry.handle(event);
521
+ }
522
+ }
523
+ } finally {
524
+ this.processing = false;
525
+ }
526
+ }
527
+ /**
528
+ * Wait for all pending events to be processed
529
+ */
530
+ async flush() {
531
+ while (this.pendingEvents.length > 0 || this.processing) {
532
+ await new Promise((resolve) => setTimeout(resolve, 10));
533
+ }
534
+ }
535
+ /**
536
+ * Get pending event count
537
+ */
538
+ get pendingCount() {
539
+ return this.pendingEvents.length;
540
+ }
541
+ /**
542
+ * Get registered patterns
543
+ */
544
+ getPatterns() {
545
+ return this.registry.getPatterns();
546
+ }
547
+ /**
548
+ * Clear all subscriptions
549
+ */
550
+ clear() {
551
+ this.registry.clear();
552
+ this.pendingEvents.length = 0;
553
+ }
554
+ /**
555
+ * Close the transport
556
+ */
557
+ async close() {
558
+ await this.flush();
559
+ this.clear();
560
+ }
561
+ };
562
+ function createMemoryEventTransport(options) {
563
+ return new MemoryEventTransport(options);
564
+ }
565
+ var GlobalEventBus = class _GlobalEventBus {
566
+ static instance = null;
567
+ transports = /* @__PURE__ */ new Map();
568
+ logger;
569
+ constructor() {
570
+ this.logger = createLogger3({ name: "global-event-bus" });
571
+ }
572
+ static getInstance() {
573
+ if (!_GlobalEventBus.instance) {
574
+ _GlobalEventBus.instance = new _GlobalEventBus();
575
+ }
576
+ return _GlobalEventBus.instance;
577
+ }
578
+ /**
579
+ * Register a service's event transport
580
+ */
581
+ register(serviceName, transport) {
582
+ if (this.transports.has(serviceName)) {
583
+ throw new Error(`Service already registered: ${serviceName}`);
584
+ }
585
+ this.transports.set(serviceName, transport);
586
+ this.logger.debug(`Service registered: ${serviceName}`);
587
+ }
588
+ /**
589
+ * Unregister a service
590
+ */
591
+ unregister(serviceName) {
592
+ const deleted = this.transports.delete(serviceName);
593
+ if (deleted) {
594
+ this.logger.debug(`Service unregistered: ${serviceName}`);
595
+ }
596
+ return deleted;
597
+ }
598
+ /**
599
+ * Broadcast an event to all services (except source)
600
+ */
601
+ async broadcast(event, excludeSource) {
602
+ const promises = [];
603
+ for (const [name, transport] of this.transports) {
604
+ if (name !== excludeSource) {
605
+ promises.push(transport.emit(event));
606
+ }
607
+ }
608
+ await Promise.allSettled(promises);
609
+ }
610
+ /**
611
+ * Send an event to a specific service
612
+ */
613
+ async send(serviceName, event) {
614
+ const transport = this.transports.get(serviceName);
615
+ if (!transport) {
616
+ this.logger.warn(`Target service not found: ${serviceName}`, {
617
+ eventId: event.id
618
+ });
619
+ return;
620
+ }
621
+ await transport.emit(event);
622
+ }
623
+ /**
624
+ * Get all registered service names
625
+ */
626
+ getServices() {
627
+ return Array.from(this.transports.keys());
628
+ }
629
+ /**
630
+ * Clear all registrations
631
+ */
632
+ clear() {
633
+ this.transports.clear();
634
+ }
635
+ /**
636
+ * Reset singleton (for testing)
637
+ */
638
+ static reset() {
639
+ _GlobalEventBus.instance = null;
640
+ }
641
+ };
642
+ function getGlobalEventBus() {
643
+ return GlobalEventBus.getInstance();
644
+ }
645
+
646
+ // src/events/dead-letter.ts
647
+ import { createLogger as createLogger4, generateId as generateId2 } from "@parsrun/core";
648
+ var DeadLetterQueue = class {
649
+ entries = /* @__PURE__ */ new Map();
650
+ resolvedOptions;
651
+ logger;
652
+ cleanupTimer = null;
653
+ constructor(options = {}) {
654
+ this.resolvedOptions = {
655
+ maxSize: options.maxSize ?? 1e3,
656
+ retentionMs: options.retentionMs ?? 30 * 24 * 60 * 60 * 1e3,
657
+ // 30 days
658
+ alertThreshold: options.alertThreshold ?? 10
659
+ };
660
+ if (options.onAdd) this.resolvedOptions.onAdd = options.onAdd;
661
+ if (options.onThreshold) this.resolvedOptions.onThreshold = options.onThreshold;
662
+ if (options.logger) this.resolvedOptions.logger = options.logger;
663
+ this.logger = options.logger ?? createLogger4({ name: "dlq" });
664
+ this.cleanupTimer = setInterval(() => this.cleanup(), 60 * 60 * 1e3);
665
+ }
666
+ /**
667
+ * Add an entry to the DLQ
668
+ */
669
+ async add(options) {
670
+ const entry = {
671
+ id: generateId2(),
672
+ event: options.event,
673
+ error: options.error,
674
+ pattern: options.pattern,
675
+ attempts: options.attempts,
676
+ addedAt: /* @__PURE__ */ new Date()
677
+ };
678
+ if (options.metadata) {
679
+ entry.metadata = options.metadata;
680
+ }
681
+ if (this.entries.size >= this.resolvedOptions.maxSize) {
682
+ const oldest = this.getOldest();
683
+ if (oldest) {
684
+ this.entries.delete(oldest.id);
685
+ this.logger.debug(`DLQ: Removed oldest entry to make room`, {
686
+ removedId: oldest.id
687
+ });
688
+ }
689
+ }
690
+ this.entries.set(entry.id, entry);
691
+ this.logger.warn(`DLQ: Entry added`, {
692
+ id: entry.id,
693
+ eventId: entry.event.id,
694
+ eventType: entry.event.type,
695
+ error: entry.error
696
+ });
697
+ this.resolvedOptions.onAdd?.(entry);
698
+ if (this.entries.size >= this.resolvedOptions.alertThreshold) {
699
+ this.resolvedOptions.onThreshold?.(this.entries.size);
700
+ }
701
+ return entry.id;
702
+ }
703
+ /**
704
+ * Get an entry by ID
705
+ */
706
+ get(id) {
707
+ return this.entries.get(id);
708
+ }
709
+ /**
710
+ * Get all entries
711
+ */
712
+ getAll() {
713
+ return Array.from(this.entries.values());
714
+ }
715
+ /**
716
+ * Get entries by event type
717
+ */
718
+ getByEventType(eventType) {
719
+ return Array.from(this.entries.values()).filter(
720
+ (e) => e.event.type === eventType
721
+ );
722
+ }
723
+ /**
724
+ * Get entries by pattern
725
+ */
726
+ getByPattern(pattern) {
727
+ return Array.from(this.entries.values()).filter(
728
+ (e) => e.pattern === pattern
729
+ );
730
+ }
731
+ /**
732
+ * Remove an entry
733
+ */
734
+ remove(id) {
735
+ const deleted = this.entries.delete(id);
736
+ if (deleted) {
737
+ this.logger.debug(`DLQ: Entry removed`, { id });
738
+ }
739
+ return deleted;
740
+ }
741
+ /**
742
+ * Retry an entry (remove from DLQ and return event)
743
+ */
744
+ retry(id) {
745
+ const entry = this.entries.get(id);
746
+ if (!entry) return void 0;
747
+ this.entries.delete(id);
748
+ this.logger.info(`DLQ: Entry removed for retry`, {
749
+ id,
750
+ eventId: entry.event.id
751
+ });
752
+ return entry.event;
753
+ }
754
+ /**
755
+ * Get count
756
+ */
757
+ get size() {
758
+ return this.entries.size;
759
+ }
760
+ /**
761
+ * Clear all entries
762
+ */
763
+ clear() {
764
+ this.entries.clear();
765
+ this.logger.info(`DLQ: Cleared all entries`);
766
+ }
767
+ /**
768
+ * Cleanup expired entries
769
+ */
770
+ cleanup() {
771
+ const now = Date.now();
772
+ let removed = 0;
773
+ for (const [id, entry] of this.entries) {
774
+ const age = now - entry.addedAt.getTime();
775
+ if (age > this.resolvedOptions.retentionMs) {
776
+ this.entries.delete(id);
777
+ removed++;
778
+ }
779
+ }
780
+ if (removed > 0) {
781
+ this.logger.debug(`DLQ: Cleaned up ${removed} expired entries`);
782
+ }
783
+ }
784
+ /**
785
+ * Get oldest entry
786
+ */
787
+ getOldest() {
788
+ let oldest;
789
+ for (const entry of this.entries.values()) {
790
+ if (!oldest || entry.addedAt < oldest.addedAt) {
791
+ oldest = entry;
792
+ }
793
+ }
794
+ return oldest;
795
+ }
796
+ /**
797
+ * Stop cleanup timer
798
+ */
799
+ close() {
800
+ if (this.cleanupTimer) {
801
+ clearInterval(this.cleanupTimer);
802
+ this.cleanupTimer = null;
803
+ }
804
+ }
805
+ /**
806
+ * Export entries for persistence
807
+ */
808
+ export() {
809
+ return Array.from(this.entries.values()).map((e) => ({
810
+ ...e,
811
+ addedAt: e.addedAt
812
+ }));
813
+ }
814
+ /**
815
+ * Import entries from persistence
816
+ */
817
+ import(entries) {
818
+ for (const entry of entries) {
819
+ this.entries.set(entry.id, {
820
+ ...entry,
821
+ addedAt: new Date(entry.addedAt)
822
+ });
823
+ }
824
+ this.logger.info(`DLQ: Imported ${entries.length} entries`);
825
+ }
826
+ };
827
+ function createDeadLetterQueue(options) {
828
+ return new DeadLetterQueue(options);
829
+ }
830
+ export {
831
+ DeadLetterQueue,
832
+ EventEmitter,
833
+ EventHandlerRegistry,
834
+ GlobalEventBus,
835
+ MemoryEventTransport,
836
+ ScopedEmitter,
837
+ createDeadLetterQueue,
838
+ createEvent,
839
+ createEventEmitter,
840
+ createEventHandlerRegistry,
841
+ createMemoryEventTransport,
842
+ createTypedEmitter,
843
+ formatEventType,
844
+ fromCompactEvent,
845
+ getGlobalEventBus,
846
+ matchEventType,
847
+ parseEventType,
848
+ toCloudEvent,
849
+ toCompactEvent,
850
+ validateCompactEvent,
851
+ validateEvent
852
+ };
853
+ //# sourceMappingURL=index.js.map