@l-etabli/events 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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -0
  3. package/dist/adapters/in-memory/InMemoryEventBus.cjs +128 -0
  4. package/dist/adapters/in-memory/InMemoryEventBus.cjs.map +1 -0
  5. package/dist/adapters/in-memory/InMemoryEventBus.d.cts +16 -0
  6. package/dist/adapters/in-memory/InMemoryEventBus.d.ts +16 -0
  7. package/dist/adapters/in-memory/InMemoryEventBus.mjs +104 -0
  8. package/dist/adapters/in-memory/InMemoryEventBus.mjs.map +1 -0
  9. package/dist/adapters/in-memory/InMemoryEventQueries.cjs +44 -0
  10. package/dist/adapters/in-memory/InMemoryEventQueries.cjs.map +1 -0
  11. package/dist/adapters/in-memory/InMemoryEventQueries.d.cts +10 -0
  12. package/dist/adapters/in-memory/InMemoryEventQueries.d.ts +10 -0
  13. package/dist/adapters/in-memory/InMemoryEventQueries.mjs +20 -0
  14. package/dist/adapters/in-memory/InMemoryEventQueries.mjs.map +1 -0
  15. package/dist/adapters/in-memory/InMemoryEventRepository.cjs +68 -0
  16. package/dist/adapters/in-memory/InMemoryEventRepository.cjs.map +1 -0
  17. package/dist/adapters/in-memory/InMemoryEventRepository.d.cts +16 -0
  18. package/dist/adapters/in-memory/InMemoryEventRepository.d.ts +16 -0
  19. package/dist/adapters/in-memory/InMemoryEventRepository.mjs +43 -0
  20. package/dist/adapters/in-memory/InMemoryEventRepository.mjs.map +1 -0
  21. package/dist/adapters/in-memory/index.cjs +43 -0
  22. package/dist/adapters/in-memory/index.cjs.map +1 -0
  23. package/dist/adapters/in-memory/index.d.cts +18 -0
  24. package/dist/adapters/in-memory/index.d.ts +18 -0
  25. package/dist/adapters/in-memory/index.mjs +18 -0
  26. package/dist/adapters/in-memory/index.mjs.map +1 -0
  27. package/dist/adapters/kysely/KyselyEventQueries.cjs +47 -0
  28. package/dist/adapters/kysely/KyselyEventQueries.cjs.map +1 -0
  29. package/dist/adapters/kysely/KyselyEventQueries.d.cts +8 -0
  30. package/dist/adapters/kysely/KyselyEventQueries.d.ts +8 -0
  31. package/dist/adapters/kysely/KyselyEventQueries.mjs +23 -0
  32. package/dist/adapters/kysely/KyselyEventQueries.mjs.map +1 -0
  33. package/dist/adapters/kysely/KyselyEventRepository.cjs +53 -0
  34. package/dist/adapters/kysely/KyselyEventRepository.cjs.map +1 -0
  35. package/dist/adapters/kysely/KyselyEventRepository.d.cts +8 -0
  36. package/dist/adapters/kysely/KyselyEventRepository.d.ts +8 -0
  37. package/dist/adapters/kysely/KyselyEventRepository.mjs +29 -0
  38. package/dist/adapters/kysely/KyselyEventRepository.mjs.map +1 -0
  39. package/dist/adapters/kysely/index.cjs +32 -0
  40. package/dist/adapters/kysely/index.cjs.map +1 -0
  41. package/dist/adapters/kysely/index.d.cts +7 -0
  42. package/dist/adapters/kysely/index.d.ts +7 -0
  43. package/dist/adapters/kysely/index.mjs +7 -0
  44. package/dist/adapters/kysely/index.mjs.map +1 -0
  45. package/dist/adapters/kysely/migration.cjs +38 -0
  46. package/dist/adapters/kysely/migration.cjs.map +1 -0
  47. package/dist/adapters/kysely/migration.d.cts +6 -0
  48. package/dist/adapters/kysely/migration.d.ts +6 -0
  49. package/dist/adapters/kysely/migration.mjs +13 -0
  50. package/dist/adapters/kysely/migration.mjs.map +1 -0
  51. package/dist/adapters/kysely/types.cjs +17 -0
  52. package/dist/adapters/kysely/types.cjs.map +1 -0
  53. package/dist/adapters/kysely/types.d.cts +34 -0
  54. package/dist/adapters/kysely/types.d.ts +34 -0
  55. package/dist/adapters/kysely/types.mjs +1 -0
  56. package/dist/adapters/kysely/types.mjs.map +1 -0
  57. package/dist/createEventCrawler.cjs +102 -0
  58. package/dist/createEventCrawler.cjs.map +1 -0
  59. package/dist/createEventCrawler.d.cts +56 -0
  60. package/dist/createEventCrawler.d.ts +56 -0
  61. package/dist/createEventCrawler.mjs +78 -0
  62. package/dist/createEventCrawler.mjs.map +1 -0
  63. package/dist/createNewEvent.cjs +43 -0
  64. package/dist/createNewEvent.cjs.map +1 -0
  65. package/dist/createNewEvent.d.cts +61 -0
  66. package/dist/createNewEvent.d.ts +61 -0
  67. package/dist/createNewEvent.mjs +19 -0
  68. package/dist/createNewEvent.mjs.map +1 -0
  69. package/dist/index.cjs +27 -0
  70. package/dist/index.cjs.map +1 -0
  71. package/dist/index.d.cts +10 -0
  72. package/dist/index.d.ts +10 -0
  73. package/dist/index.mjs +4 -0
  74. package/dist/index.mjs.map +1 -0
  75. package/dist/ports/EventBus.cjs +17 -0
  76. package/dist/ports/EventBus.cjs.map +1 -0
  77. package/dist/ports/EventBus.d.cts +35 -0
  78. package/dist/ports/EventBus.d.ts +35 -0
  79. package/dist/ports/EventBus.mjs +1 -0
  80. package/dist/ports/EventBus.mjs.map +1 -0
  81. package/dist/ports/EventQueries.cjs +17 -0
  82. package/dist/ports/EventQueries.cjs.map +1 -0
  83. package/dist/ports/EventQueries.d.cts +24 -0
  84. package/dist/ports/EventQueries.d.ts +24 -0
  85. package/dist/ports/EventQueries.mjs +1 -0
  86. package/dist/ports/EventQueries.mjs.map +1 -0
  87. package/dist/ports/EventRepository.cjs +17 -0
  88. package/dist/ports/EventRepository.cjs.map +1 -0
  89. package/dist/ports/EventRepository.d.cts +43 -0
  90. package/dist/ports/EventRepository.d.ts +43 -0
  91. package/dist/ports/EventRepository.mjs +1 -0
  92. package/dist/ports/EventRepository.mjs.map +1 -0
  93. package/dist/types.cjs +17 -0
  94. package/dist/types.cjs.map +1 -0
  95. package/dist/types.d.cts +84 -0
  96. package/dist/types.d.ts +84 -0
  97. package/dist/types.mjs +1 -0
  98. package/dist/types.mjs.map +1 -0
  99. package/package.json +68 -0
  100. package/src/adapters/in-memory/InMemoryEventBus.ts +165 -0
  101. package/src/adapters/in-memory/InMemoryEventQueries.ts +30 -0
  102. package/src/adapters/in-memory/InMemoryEventRepository.ts +61 -0
  103. package/src/adapters/in-memory/index.ts +19 -0
  104. package/src/adapters/kysely/KyselyEventQueries.ts +35 -0
  105. package/src/adapters/kysely/KyselyEventRepository.ts +44 -0
  106. package/src/adapters/kysely/index.ts +3 -0
  107. package/src/adapters/kysely/migration.ts +39 -0
  108. package/src/adapters/kysely/types.ts +37 -0
  109. package/src/createEventCrawler.ts +139 -0
  110. package/src/createNewEvent.ts +87 -0
  111. package/src/index.ts +7 -0
  112. package/src/ports/EventBus.ts +37 -0
  113. package/src/ports/EventQueries.ts +25 -0
  114. package/src/ports/EventRepository.ts +49 -0
  115. package/src/types.ts +100 -0
@@ -0,0 +1,37 @@
1
+ import type { DefaultContext, GenericEvent, SubscriptionId } from "../types.ts";
2
+
3
+ /**
4
+ * Event bus for publishing events to subscribers.
5
+ * Handles delivery, failure tracking, and retry logic.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * eventBus.subscribe({
10
+ * topic: "OrderPlaced",
11
+ * subscriptionId: "send-confirmation-email",
12
+ * callBack: async (event) => {
13
+ * await emailService.sendOrderConfirmation(event.payload);
14
+ * },
15
+ * });
16
+ * ```
17
+ */
18
+ export type EventBus<
19
+ Event extends GenericEvent<string, unknown, DefaultContext>,
20
+ > = {
21
+ /**
22
+ * Publish an event to all subscribers of its topic.
23
+ * Updates event status and records publication results.
24
+ */
25
+ publish: (event: Event) => Promise<void>;
26
+ /**
27
+ * Register a subscriber for a specific topic.
28
+ * The subscriptionId must be unique per topic and is used for retry tracking.
29
+ */
30
+ subscribe: <
31
+ Event extends GenericEvent<string, unknown, DefaultContext>,
32
+ >(params: {
33
+ topic: Event["topic"];
34
+ subscriptionId: SubscriptionId;
35
+ callBack: (e: Event) => Promise<void>;
36
+ }) => void;
37
+ };
@@ -0,0 +1,25 @@
1
+ import type { DefaultContext, EventStatus, GenericEvent } from "../types.ts";
2
+
3
+ /** Parameters for querying events. */
4
+ type GetEventsParams = {
5
+ filters: {
6
+ /** Filter by event status (e.g., ["never-published", "failed-but-will-retry"]). */
7
+ statuses: EventStatus[];
8
+ /** Optional context filter for multi-tenant scenarios. */
9
+ context?: Partial<Record<string, string>>;
10
+ };
11
+ /** Maximum number of events to return. */
12
+ limit: number;
13
+ };
14
+
15
+ /**
16
+ * Query interface for reading events.
17
+ * Used by the event crawler to fetch events for processing.
18
+ * Implement this to query events from your database.
19
+ */
20
+ export type EventQueries<
21
+ Event extends GenericEvent<string, unknown, DefaultContext>,
22
+ > = {
23
+ /** Fetch events matching the given filters. */
24
+ getEvents: (params: GetEventsParams) => Promise<Event[]>;
25
+ };
@@ -0,0 +1,49 @@
1
+ import type { DefaultContext, GenericEvent } from "../types.ts";
2
+
3
+ /**
4
+ * Repository interface for persisting events.
5
+ * Implement this to store events in your database (e.g., PostgreSQL, MongoDB).
6
+ * Events should be saved in the same transaction as your domain changes.
7
+ */
8
+ export type EventRepository<
9
+ Event extends GenericEvent<string, unknown, DefaultContext>,
10
+ > = {
11
+ /** Persist a single event (typically after publication status update). */
12
+ save: (event: Event) => Promise<void>;
13
+ /** Persist multiple new events in a batch. */
14
+ saveNewEventsBatch: (events: Event[]) => Promise<void>;
15
+ /** Mark events as "in-process" before publishing (prevents duplicate processing). */
16
+ markEventsAsInProcess: (events: Event[]) => Promise<void>;
17
+ };
18
+
19
+ /**
20
+ * Unit of work containing the event repository.
21
+ * Extend this with your own repositories for transactional consistency.
22
+ */
23
+ export type EventsUnitOfWork<
24
+ Event extends GenericEvent<string, unknown, DefaultContext>,
25
+ > = {
26
+ eventRepository: EventRepository<Event>;
27
+ };
28
+
29
+ /**
30
+ * Higher-order function that provides a unit of work for transactional operations.
31
+ * Your implementation should handle transaction begin/commit/rollback.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const withUow: WithEventsUow<MyEvent> = async (fn) => {
36
+ * const tx = await db.beginTransaction();
37
+ * try {
38
+ * await fn({ eventRepository: createEventRepo(tx) });
39
+ * await tx.commit();
40
+ * } catch (e) {
41
+ * await tx.rollback();
42
+ * throw e;
43
+ * }
44
+ * };
45
+ * ```
46
+ */
47
+ export type WithEventsUow<
48
+ Event extends GenericEvent<string, unknown, DefaultContext>,
49
+ > = (fn: (uow: EventsUnitOfWork<Event>) => Promise<void>) => Promise<void>;
package/src/types.ts ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Branded type helper for nominal typing.
3
+ * Adds a phantom type property to distinguish between structurally identical types.
4
+ */
5
+ export type Flavor<T, FlavorT> = T & {
6
+ _type?: FlavorT;
7
+ };
8
+
9
+ /** Unique identifier for an event subscription. */
10
+ export type SubscriptionId = Flavor<string, "SubscriptionId">;
11
+
12
+ /** Unique identifier for a user who triggered an event. */
13
+ export type UserId = Flavor<string, "UserId">;
14
+
15
+ /** Unique identifier for an event. */
16
+ export type EventId = Flavor<string, "EventId">;
17
+
18
+ /**
19
+ * Records a subscription failure during event publication.
20
+ * Contains the subscription that failed and error details for debugging.
21
+ */
22
+ export type EventFailure = {
23
+ subscriptionId: SubscriptionId;
24
+ errorMessage: string;
25
+ /** Stack trace captured when the subscription callback threw. */
26
+ stack?: string;
27
+ };
28
+
29
+ /**
30
+ * Records a single publication attempt for an event.
31
+ * Tracks which subscribers were notified and any failures that occurred.
32
+ */
33
+ export type EventPublication = {
34
+ publishedAt: Date;
35
+ /** All subscription IDs that were attempted in this publication. */
36
+ publishedSubscribers: SubscriptionId[];
37
+ /** Subscriptions that failed during this publication attempt. */
38
+ failures: EventFailure[];
39
+ };
40
+
41
+ /**
42
+ * Event lifecycle status.
43
+ * - `never-published`: New event, not yet processed by crawler
44
+ * - `in-process`: Currently being published by crawler
45
+ * - `published`: Successfully delivered to all subscribers
46
+ * - `failed-but-will-retry`: Some subscribers failed, will retry
47
+ * - `quarantined`: Exceeded max retries, requires manual intervention
48
+ * - `to-republish`: Force republish to all subscribers (manual trigger)
49
+ */
50
+ export type EventStatus =
51
+ | "never-published"
52
+ | "to-republish"
53
+ | "in-process"
54
+ | "published"
55
+ | "failed-but-will-retry"
56
+ | "quarantined";
57
+
58
+ /** Context type constraint - must be a string record or undefined. */
59
+ export type DefaultContext = Record<string, string> | undefined;
60
+
61
+ /**
62
+ * Generic event type for the outbox pattern.
63
+ * Events are persisted in the same transaction as domain changes,
64
+ * then asynchronously published to subscribers.
65
+ *
66
+ * @typeParam T - Event topic/type string literal
67
+ * @typeParam P - Event payload type
68
+ * @typeParam C - Optional context for filtering (e.g., tenant ID)
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * type MyEvents =
73
+ * | GenericEvent<"UserCreated", { userId: string; email: string }>
74
+ * | GenericEvent<"OrderPlaced", { orderId: string }, { tenantId: string }>;
75
+ * ```
76
+ */
77
+ export type GenericEvent<
78
+ T extends string,
79
+ P,
80
+ C extends DefaultContext = undefined,
81
+ > = {
82
+ /** Unique event identifier. */
83
+ id: EventId;
84
+ /** When the event occurred in the domain. */
85
+ occurredAt: Date;
86
+ /** Event type/topic for routing to subscribers. */
87
+ topic: T;
88
+ /** Event-specific data. */
89
+ payload: P;
90
+ /** Current lifecycle status. */
91
+ status: EventStatus;
92
+ /** History of publication attempts. */
93
+ publications: EventPublication[];
94
+ /** User who triggered the action that created this event. */
95
+ triggeredByUserId: UserId;
96
+ /** Optional priority for processing order (not yet implemented in crawler). */
97
+ priority?: number;
98
+ /** Optional context for filtering events (e.g., by tenant). */
99
+ context?: C;
100
+ };