@l-etabli/events 0.6.0 → 0.7.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/README.md +96 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventQueries.cjs +54 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventQueries.cjs.map +1 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventQueries.d.cts +10 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventQueries.d.ts +10 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventQueries.mjs +30 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventQueries.mjs.map +1 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventRepository.cjs +85 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventRepository.cjs.map +1 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventRepository.d.cts +9 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventRepository.d.ts +9 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventRepository.mjs +61 -0
- package/dist/adapters/effect-kysely/EffectKyselyEventRepository.mjs.map +1 -0
- package/dist/adapters/effect-kysely/index.cjs +32 -0
- package/dist/adapters/effect-kysely/index.cjs.map +1 -0
- package/dist/adapters/effect-kysely/index.d.cts +9 -0
- package/dist/adapters/effect-kysely/index.d.ts +9 -0
- package/dist/adapters/effect-kysely/index.mjs +7 -0
- package/dist/adapters/effect-kysely/index.mjs.map +1 -0
- package/dist/adapters/in-memory/InMemoryEventBus.cjs +3 -17
- package/dist/adapters/in-memory/InMemoryEventBus.cjs.map +1 -1
- package/dist/adapters/in-memory/InMemoryEventBus.mjs +2 -16
- package/dist/adapters/in-memory/InMemoryEventBus.mjs.map +1 -1
- package/dist/adapters/in-memory/InMemoryEventQueries.cjs +2 -20
- package/dist/adapters/in-memory/InMemoryEventQueries.cjs.map +1 -1
- package/dist/adapters/in-memory/InMemoryEventQueries.mjs +2 -20
- package/dist/adapters/in-memory/InMemoryEventQueries.mjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventQueries.cjs +20 -24
- package/dist/adapters/kysely/KyselyEventQueries.cjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventQueries.d.cts +1 -1
- package/dist/adapters/kysely/KyselyEventQueries.d.ts +1 -1
- package/dist/adapters/kysely/KyselyEventQueries.mjs +20 -24
- package/dist/adapters/kysely/KyselyEventQueries.mjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventRepository.cjs +47 -45
- package/dist/adapters/kysely/KyselyEventRepository.cjs.map +1 -1
- package/dist/adapters/kysely/KyselyEventRepository.d.cts +1 -1
- package/dist/adapters/kysely/KyselyEventRepository.d.ts +1 -1
- package/dist/adapters/kysely/KyselyEventRepository.mjs +43 -41
- package/dist/adapters/kysely/KyselyEventRepository.mjs.map +1 -1
- package/dist/adapters/kysely/jsonb.cjs +30 -0
- package/dist/adapters/kysely/jsonb.cjs.map +1 -0
- package/dist/adapters/kysely/jsonb.d.cts +5 -0
- package/dist/adapters/kysely/jsonb.d.ts +5 -0
- package/dist/adapters/kysely/jsonb.mjs +6 -0
- package/dist/adapters/kysely/jsonb.mjs.map +1 -0
- package/dist/adapters/kysely/mapEventRow.cjs +35 -0
- package/dist/adapters/kysely/mapEventRow.cjs.map +1 -0
- package/dist/adapters/kysely/mapEventRow.d.cts +6 -0
- package/dist/adapters/kysely/mapEventRow.d.ts +6 -0
- package/dist/adapters/kysely/mapEventRow.mjs +11 -0
- package/dist/adapters/kysely/mapEventRow.mjs.map +1 -0
- package/dist/createEventCrawler.cjs +2 -8
- package/dist/createEventCrawler.cjs.map +1 -1
- package/dist/createEventCrawler.mjs +1 -7
- package/dist/createEventCrawler.mjs.map +1 -1
- package/dist/effect/EffectEventCrawler.cjs +111 -0
- package/dist/effect/EffectEventCrawler.cjs.map +1 -0
- package/dist/effect/EffectEventCrawler.d.cts +26 -0
- package/dist/effect/EffectEventCrawler.d.ts +26 -0
- package/dist/effect/EffectEventCrawler.mjs +87 -0
- package/dist/effect/EffectEventCrawler.mjs.map +1 -0
- package/dist/effect/EffectInMemoryEventBus.cjs +131 -0
- package/dist/effect/EffectInMemoryEventBus.cjs.map +1 -0
- package/dist/effect/EffectInMemoryEventBus.d.cts +31 -0
- package/dist/effect/EffectInMemoryEventBus.d.ts +31 -0
- package/dist/effect/EffectInMemoryEventBus.mjs +112 -0
- package/dist/effect/EffectInMemoryEventBus.mjs.map +1 -0
- package/dist/effect/EffectInMemoryEventQueries.cjs +35 -0
- package/dist/effect/EffectInMemoryEventQueries.cjs.map +1 -0
- package/dist/effect/EffectInMemoryEventQueries.d.cts +12 -0
- package/dist/effect/EffectInMemoryEventQueries.d.ts +12 -0
- package/dist/effect/EffectInMemoryEventQueries.mjs +11 -0
- package/dist/effect/EffectInMemoryEventQueries.mjs.map +1 -0
- package/dist/effect/EffectInMemoryEventRepository.cjs +73 -0
- package/dist/effect/EffectInMemoryEventRepository.cjs.map +1 -0
- package/dist/effect/EffectInMemoryEventRepository.d.cts +15 -0
- package/dist/effect/EffectInMemoryEventRepository.d.ts +15 -0
- package/dist/effect/EffectInMemoryEventRepository.mjs +48 -0
- package/dist/effect/EffectInMemoryEventRepository.mjs.map +1 -0
- package/dist/effect/EffectSubscriptions.cjs +61 -0
- package/dist/effect/EffectSubscriptions.cjs.map +1 -0
- package/dist/effect/EffectSubscriptions.d.cts +22 -0
- package/dist/effect/EffectSubscriptions.d.ts +22 -0
- package/dist/effect/EffectSubscriptions.mjs +36 -0
- package/dist/effect/EffectSubscriptions.mjs.map +1 -0
- package/dist/effect/index.cjs +47 -0
- package/dist/effect/index.cjs.map +1 -0
- package/dist/effect/index.d.cts +27 -0
- package/dist/effect/index.d.ts +27 -0
- package/dist/effect/index.mjs +20 -0
- package/dist/effect/index.mjs.map +1 -0
- package/dist/effect/ports/EffectEventBus.cjs +17 -0
- package/dist/effect/ports/EffectEventBus.cjs.map +1 -0
- package/dist/effect/ports/EffectEventBus.d.cts +13 -0
- package/dist/effect/ports/EffectEventBus.d.ts +13 -0
- package/dist/effect/ports/EffectEventBus.mjs +1 -0
- package/dist/effect/ports/EffectEventBus.mjs.map +1 -0
- package/dist/effect/ports/EffectEventQueries.cjs +17 -0
- package/dist/effect/ports/EffectEventQueries.cjs.map +1 -0
- package/dist/effect/ports/EffectEventQueries.d.cts +9 -0
- package/dist/effect/ports/EffectEventQueries.d.ts +9 -0
- package/dist/effect/ports/EffectEventQueries.mjs +1 -0
- package/dist/effect/ports/EffectEventQueries.mjs.map +1 -0
- package/dist/effect/ports/EffectEventRepository.cjs +17 -0
- package/dist/effect/ports/EffectEventRepository.cjs.map +1 -0
- package/dist/effect/ports/EffectEventRepository.d.cts +17 -0
- package/dist/effect/ports/EffectEventRepository.d.ts +17 -0
- package/dist/effect/ports/EffectEventRepository.mjs +1 -0
- package/dist/effect/ports/EffectEventRepository.mjs.map +1 -0
- package/dist/filterEvents.cjs +48 -0
- package/dist/filterEvents.cjs.map +1 -0
- package/dist/filterEvents.d.cts +6 -0
- package/dist/filterEvents.d.ts +6 -0
- package/dist/filterEvents.mjs +24 -0
- package/dist/filterEvents.mjs.map +1 -0
- package/dist/getSubscriptionIdsToPublish.cjs +40 -0
- package/dist/getSubscriptionIdsToPublish.cjs.map +1 -0
- package/dist/getSubscriptionIdsToPublish.d.cts +5 -0
- package/dist/getSubscriptionIdsToPublish.d.ts +5 -0
- package/dist/getSubscriptionIdsToPublish.mjs +16 -0
- package/dist/getSubscriptionIdsToPublish.mjs.map +1 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/ports/EventQueries.cjs.map +1 -1
- package/dist/ports/EventQueries.d.cts +1 -1
- package/dist/ports/EventQueries.d.ts +1 -1
- package/dist/splitIntoChunks.cjs +35 -0
- package/dist/splitIntoChunks.cjs.map +1 -0
- package/dist/splitIntoChunks.d.cts +3 -0
- package/dist/splitIntoChunks.d.ts +3 -0
- package/dist/splitIntoChunks.mjs +11 -0
- package/dist/splitIntoChunks.mjs.map +1 -0
- package/package.json +18 -3
- package/src/adapters/effect-kysely/EffectKyselyEventQueries.ts +45 -0
- package/src/adapters/effect-kysely/EffectKyselyEventRepository.ts +90 -0
- package/src/adapters/effect-kysely/index.ts +3 -0
- package/src/adapters/in-memory/InMemoryEventBus.ts +2 -23
- package/src/adapters/in-memory/InMemoryEventQueries.ts +2 -32
- package/src/adapters/kysely/KyselyEventQueries.ts +27 -31
- package/src/adapters/kysely/KyselyEventRepository.ts +66 -64
- package/src/adapters/kysely/jsonb.ts +4 -0
- package/src/adapters/kysely/mapEventRow.ts +15 -0
- package/src/createEventCrawler.ts +1 -8
- package/src/effect/EffectEventCrawler.ts +124 -0
- package/src/effect/EffectInMemoryEventBus.ts +231 -0
- package/src/effect/EffectInMemoryEventQueries.ts +16 -0
- package/src/effect/EffectInMemoryEventRepository.ts +68 -0
- package/src/effect/EffectSubscriptions.ts +74 -0
- package/src/effect/index.ts +26 -0
- package/src/effect/ports/EffectEventBus.ts +17 -0
- package/src/effect/ports/EffectEventQueries.ts +9 -0
- package/src/effect/ports/EffectEventRepository.ts +27 -0
- package/src/filterEvents.ts +39 -0
- package/src/getSubscriptionIdsToPublish.ts +21 -0
- package/src/ports/EventQueries.ts +1 -1
- package/src/splitIntoChunks.ts +7 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Cause, Effect, Exit } from "effect";
|
|
2
|
+
import {
|
|
3
|
+
type CreateNewEvent,
|
|
4
|
+
type CreateNewEventFromDefinitions,
|
|
5
|
+
makeCreateNewEvent,
|
|
6
|
+
} from "../createNewEvent.ts";
|
|
7
|
+
import type {
|
|
8
|
+
EventDefinitions,
|
|
9
|
+
InferEventsFromDefinitions,
|
|
10
|
+
} from "../eventDefinitions.ts";
|
|
11
|
+
import { getSubscriptionIdsToPublish } from "../getSubscriptionIdsToPublish.ts";
|
|
12
|
+
import type {
|
|
13
|
+
DefaultContext,
|
|
14
|
+
EventId,
|
|
15
|
+
EventPublication,
|
|
16
|
+
GenericEvent,
|
|
17
|
+
SubscriptionId,
|
|
18
|
+
} from "../types.ts";
|
|
19
|
+
import {
|
|
20
|
+
type GlobalSubscriberConfig,
|
|
21
|
+
subscribeByTopic,
|
|
22
|
+
subscribeGlobalToTopics,
|
|
23
|
+
type TopicSubscriptions,
|
|
24
|
+
} from "./EffectSubscriptions.ts";
|
|
25
|
+
import type { EventBus } from "./ports/EffectEventBus.ts";
|
|
26
|
+
import type { WithEventsUow } from "./ports/EffectEventRepository.ts";
|
|
27
|
+
|
|
28
|
+
type SubscriptionsForTopic = Record<
|
|
29
|
+
string,
|
|
30
|
+
(event: GenericEvent<string, unknown, DefaultContext>) => Effect.Effect<void>
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
type CreateEffectInMemoryEventBusOptions = {
|
|
34
|
+
maxRetries?: number;
|
|
35
|
+
getNow?: () => Date;
|
|
36
|
+
generateId?: () => EventId;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type CreateEffectInMemoryEventBusFromDefinitionsOptions<
|
|
40
|
+
Definitions extends EventDefinitions,
|
|
41
|
+
> = CreateEffectInMemoryEventBusOptions & {
|
|
42
|
+
eventDefinitions: Definitions;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type CreateEffectInMemoryEventBusResult<
|
|
46
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
47
|
+
> = {
|
|
48
|
+
eventBus: EventBus<Event>;
|
|
49
|
+
createNewEvent: CreateNewEvent<Event>;
|
|
50
|
+
defineSubscriptions: (
|
|
51
|
+
subscriptions: TopicSubscriptions<Event>,
|
|
52
|
+
) => TopicSubscriptions<Event>;
|
|
53
|
+
subscribeAll: (subscriptions: TopicSubscriptions<Event>) => void;
|
|
54
|
+
subscribeGlobal: (
|
|
55
|
+
subscriptions: TopicSubscriptions<Event>,
|
|
56
|
+
config: GlobalSubscriberConfig<Event>,
|
|
57
|
+
) => void;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export function createEffectInMemoryEventBus<
|
|
61
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
62
|
+
>(
|
|
63
|
+
withUow: WithEventsUow<Event>,
|
|
64
|
+
options?: CreateEffectInMemoryEventBusOptions,
|
|
65
|
+
): CreateEffectInMemoryEventBusResult<Event>;
|
|
66
|
+
export function createEffectInMemoryEventBus<
|
|
67
|
+
Definitions extends EventDefinitions,
|
|
68
|
+
>(
|
|
69
|
+
withUow: WithEventsUow<InferEventsFromDefinitions<Definitions>>,
|
|
70
|
+
options: CreateEffectInMemoryEventBusFromDefinitionsOptions<Definitions>,
|
|
71
|
+
): Omit<
|
|
72
|
+
CreateEffectInMemoryEventBusResult<InferEventsFromDefinitions<Definitions>>,
|
|
73
|
+
"createNewEvent"
|
|
74
|
+
> & {
|
|
75
|
+
createNewEvent: CreateNewEventFromDefinitions<Definitions>;
|
|
76
|
+
};
|
|
77
|
+
export function createEffectInMemoryEventBus<
|
|
78
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
79
|
+
>(
|
|
80
|
+
withUow: WithEventsUow<Event>,
|
|
81
|
+
options:
|
|
82
|
+
| CreateEffectInMemoryEventBusOptions
|
|
83
|
+
| CreateEffectInMemoryEventBusFromDefinitionsOptions<EventDefinitions> = {},
|
|
84
|
+
) {
|
|
85
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
86
|
+
const eventDefinitions =
|
|
87
|
+
"eventDefinitions" in options ? options.eventDefinitions : undefined;
|
|
88
|
+
const createNewEvent = eventDefinitions
|
|
89
|
+
? makeCreateNewEvent({
|
|
90
|
+
getNow: options.getNow,
|
|
91
|
+
generateId: options.generateId,
|
|
92
|
+
eventDefinitions,
|
|
93
|
+
})
|
|
94
|
+
: makeCreateNewEvent<Event>({
|
|
95
|
+
getNow: options.getNow,
|
|
96
|
+
generateId: options.generateId,
|
|
97
|
+
});
|
|
98
|
+
const subscriptions: Partial<Record<string, SubscriptionsForTopic>> = {};
|
|
99
|
+
|
|
100
|
+
const executeCallback = (
|
|
101
|
+
event: Event,
|
|
102
|
+
subscriptionId: string,
|
|
103
|
+
callback: (
|
|
104
|
+
event: GenericEvent<string, unknown, DefaultContext>,
|
|
105
|
+
) => Effect.Effect<void>,
|
|
106
|
+
): Effect.Effect<
|
|
107
|
+
{ subscriptionId: string; errorMessage: string; stack?: string } | undefined
|
|
108
|
+
> =>
|
|
109
|
+
Effect.map(
|
|
110
|
+
Effect.exit(callback(event)),
|
|
111
|
+
(
|
|
112
|
+
exit,
|
|
113
|
+
):
|
|
114
|
+
| { subscriptionId: string; errorMessage: string; stack?: string }
|
|
115
|
+
| undefined => {
|
|
116
|
+
if (Exit.isSuccess(exit)) return undefined;
|
|
117
|
+
const error = Cause.squash(exit.cause);
|
|
118
|
+
return {
|
|
119
|
+
subscriptionId,
|
|
120
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
121
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const eventBus: EventBus<Event> = {
|
|
127
|
+
publish: (event) =>
|
|
128
|
+
Effect.gen(function* () {
|
|
129
|
+
const publishedAt = new Date();
|
|
130
|
+
const topic = event.topic;
|
|
131
|
+
|
|
132
|
+
const callbacksBySubscriptionSlug = subscriptions[topic];
|
|
133
|
+
|
|
134
|
+
if (!callbacksBySubscriptionSlug) {
|
|
135
|
+
event.publications.push({
|
|
136
|
+
publishedAt,
|
|
137
|
+
publishedSubscribers: [],
|
|
138
|
+
});
|
|
139
|
+
event.status = "published";
|
|
140
|
+
yield* withUow((uow) => uow.eventRepository.save(event));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const subscriptionIdsToPublish = getSubscriptionIdsToPublish(
|
|
145
|
+
event,
|
|
146
|
+
Object.keys(callbacksBySubscriptionSlug),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const failuresOrUndefined = yield* Effect.all(
|
|
150
|
+
subscriptionIdsToPublish.map((subscriptionId) =>
|
|
151
|
+
executeCallback(
|
|
152
|
+
event,
|
|
153
|
+
subscriptionId,
|
|
154
|
+
callbacksBySubscriptionSlug[subscriptionId],
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
{ concurrency: "unbounded" },
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const failures = failuresOrUndefined.filter(
|
|
161
|
+
(
|
|
162
|
+
f,
|
|
163
|
+
): f is {
|
|
164
|
+
subscriptionId: string;
|
|
165
|
+
errorMessage: string;
|
|
166
|
+
stack?: string;
|
|
167
|
+
} => f !== undefined,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const publications: EventPublication[] = [
|
|
171
|
+
...event.publications,
|
|
172
|
+
{
|
|
173
|
+
publishedAt,
|
|
174
|
+
publishedSubscribers: subscriptionIdsToPublish.map(
|
|
175
|
+
(id) => id as SubscriptionId,
|
|
176
|
+
),
|
|
177
|
+
...(failures.length > 0 && { failures }),
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
if (failures.length === 0) {
|
|
182
|
+
event.status = "published";
|
|
183
|
+
} else {
|
|
184
|
+
const wasMaxNumberOfErrorsReached = publications.length >= maxRetries;
|
|
185
|
+
event.status = wasMaxNumberOfErrorsReached
|
|
186
|
+
? "quarantined"
|
|
187
|
+
: "failed-but-will-retry";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
event.publications = publications;
|
|
191
|
+
|
|
192
|
+
yield* withUow((uow) => uow.eventRepository.save(event));
|
|
193
|
+
}),
|
|
194
|
+
|
|
195
|
+
subscribe: ({ topic, subscriptionId, callBack }) => {
|
|
196
|
+
if (!subscriptions[topic]) {
|
|
197
|
+
subscriptions[topic] = {};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const subscriptionsForTopic = subscriptions[topic];
|
|
201
|
+
if (subscriptionsForTopic) {
|
|
202
|
+
subscriptionsForTopic[subscriptionId] = callBack as (
|
|
203
|
+
event: GenericEvent<string, unknown, DefaultContext>,
|
|
204
|
+
) => Effect.Effect<void>;
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const defineSubscriptions = (
|
|
210
|
+
subs: TopicSubscriptions<Event>,
|
|
211
|
+
): TopicSubscriptions<Event> => subs;
|
|
212
|
+
|
|
213
|
+
const subscribeAll = (subs: TopicSubscriptions<Event>): void => {
|
|
214
|
+
subscribeByTopic(eventBus, subs);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const subscribeGlobal = (
|
|
218
|
+
subs: TopicSubscriptions<Event>,
|
|
219
|
+
config: GlobalSubscriberConfig<Event>,
|
|
220
|
+
): void => {
|
|
221
|
+
subscribeGlobalToTopics(eventBus, subs, config);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
eventBus,
|
|
226
|
+
createNewEvent,
|
|
227
|
+
defineSubscriptions,
|
|
228
|
+
subscribeAll,
|
|
229
|
+
subscribeGlobal,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { filterEvents } from "../filterEvents.ts";
|
|
3
|
+
import type { DefaultContext, GenericEvent } from "../types.ts";
|
|
4
|
+
import type { InMemoryEventRepositoryHelpers } from "./EffectInMemoryEventRepository.ts";
|
|
5
|
+
import type { EventQueries } from "./ports/EffectEventQueries.ts";
|
|
6
|
+
|
|
7
|
+
export const createEffectInMemoryEventQueries = <
|
|
8
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
9
|
+
>(
|
|
10
|
+
helpers: InMemoryEventRepositoryHelpers<Event>,
|
|
11
|
+
): { eventQueries: EventQueries<Event> } => ({
|
|
12
|
+
eventQueries: {
|
|
13
|
+
getEvents: (params) =>
|
|
14
|
+
Effect.sync(() => filterEvents(helpers.getAllEvents(), params)),
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { InMemoryEventRepositoryHelpers } from "../adapters/in-memory/InMemoryEventRepository.ts";
|
|
3
|
+
import type { DefaultContext, GenericEvent } from "../types.ts";
|
|
4
|
+
import type {
|
|
5
|
+
EventRepository,
|
|
6
|
+
WithEventsUow,
|
|
7
|
+
} from "./ports/EffectEventRepository.ts";
|
|
8
|
+
|
|
9
|
+
export type { InMemoryEventRepositoryHelpers };
|
|
10
|
+
|
|
11
|
+
export const createEffectInMemoryEventRepository = <
|
|
12
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
13
|
+
>(): {
|
|
14
|
+
eventRepository: EventRepository<Event>;
|
|
15
|
+
helpers: InMemoryEventRepositoryHelpers<Event>;
|
|
16
|
+
} => {
|
|
17
|
+
const eventById: Record<string, Event> = {};
|
|
18
|
+
|
|
19
|
+
const eventRepository: EventRepository<Event> = {
|
|
20
|
+
save: (event) =>
|
|
21
|
+
Effect.sync(() => {
|
|
22
|
+
eventById[event.id] = event;
|
|
23
|
+
}),
|
|
24
|
+
saveNewEventsBatch: (events) =>
|
|
25
|
+
Effect.sync(() => {
|
|
26
|
+
for (const event of events) {
|
|
27
|
+
eventById[event.id] = event;
|
|
28
|
+
}
|
|
29
|
+
}),
|
|
30
|
+
markEventsAsInProcess: (events) =>
|
|
31
|
+
Effect.sync(() => {
|
|
32
|
+
for (const event of events) {
|
|
33
|
+
eventById[event.id] = { ...event, status: "in-process" };
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
eventRepository,
|
|
40
|
+
helpers: {
|
|
41
|
+
getAllEvents: () => Object.values(eventById),
|
|
42
|
+
setEvents: (events) => {
|
|
43
|
+
for (const key of Object.keys(eventById)) {
|
|
44
|
+
delete eventById[key];
|
|
45
|
+
}
|
|
46
|
+
for (const event of events) {
|
|
47
|
+
eventById[event.id] = event;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const createEffectInMemoryWithUow = <
|
|
55
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
56
|
+
>(
|
|
57
|
+
eventRepository: EventRepository<Event>,
|
|
58
|
+
): { withUow: WithEventsUow<Event> } => {
|
|
59
|
+
const withUow: WithEventsUow<Event> = (fn, options) =>
|
|
60
|
+
Effect.gen(function* () {
|
|
61
|
+
const result = yield* fn({ eventRepository });
|
|
62
|
+
if (options?.afterCommit) {
|
|
63
|
+
yield* options.afterCommit();
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
});
|
|
67
|
+
return { withUow };
|
|
68
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
import type { NarrowEvent, TopicFilter } from "../subscriptions.ts";
|
|
3
|
+
import type { DefaultContext, GenericEvent, SubscriptionId } from "../types.ts";
|
|
4
|
+
import type { EventBus } from "./ports/EffectEventBus.ts";
|
|
5
|
+
|
|
6
|
+
export type { NarrowEvent, TopicFilter };
|
|
7
|
+
|
|
8
|
+
export type TopicSubscriber<
|
|
9
|
+
E extends GenericEvent<string, unknown, DefaultContext>,
|
|
10
|
+
> = {
|
|
11
|
+
subscriptionId: SubscriptionId;
|
|
12
|
+
handler: (event: E) => Effect.Effect<void>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TopicSubscriptions<
|
|
16
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
17
|
+
> = {
|
|
18
|
+
[K in AllEvents["topic"]]: Array<TopicSubscriber<NarrowEvent<AllEvents, K>>>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type GlobalSubscriberConfig<
|
|
22
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
23
|
+
> = {
|
|
24
|
+
subscriptionId: SubscriptionId;
|
|
25
|
+
handler: (event: AllEvents) => Effect.Effect<void>;
|
|
26
|
+
filter?: TopicFilter<AllEvents["topic"]>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function subscribeByTopic<
|
|
30
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
31
|
+
>(
|
|
32
|
+
eventBus: EventBus<AllEvents>,
|
|
33
|
+
subscriptions: TopicSubscriptions<AllEvents>,
|
|
34
|
+
): void {
|
|
35
|
+
for (const topic of Object.keys(subscriptions) as AllEvents["topic"][]) {
|
|
36
|
+
const handlers = subscriptions[topic];
|
|
37
|
+
for (const { subscriptionId, handler } of handlers) {
|
|
38
|
+
eventBus.subscribe({
|
|
39
|
+
topic,
|
|
40
|
+
subscriptionId,
|
|
41
|
+
callBack: handler,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function subscribeGlobalToTopics<
|
|
48
|
+
AllEvents extends GenericEvent<string, unknown, DefaultContext>,
|
|
49
|
+
>(
|
|
50
|
+
eventBus: EventBus<AllEvents>,
|
|
51
|
+
subscriptions: TopicSubscriptions<AllEvents>,
|
|
52
|
+
config: GlobalSubscriberConfig<AllEvents>,
|
|
53
|
+
): void {
|
|
54
|
+
const allTopics = Object.keys(subscriptions) as AllEvents["topic"][];
|
|
55
|
+
|
|
56
|
+
let topicsToSubscribe: AllEvents["topic"][];
|
|
57
|
+
|
|
58
|
+
const filter = config.filter;
|
|
59
|
+
if (!filter) {
|
|
60
|
+
topicsToSubscribe = allTopics;
|
|
61
|
+
} else if ("include" in filter) {
|
|
62
|
+
topicsToSubscribe = filter.include;
|
|
63
|
+
} else {
|
|
64
|
+
topicsToSubscribe = allTopics.filter((t) => !filter.exclude.includes(t));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const topic of topicsToSubscribe) {
|
|
68
|
+
eventBus.subscribe({
|
|
69
|
+
topic,
|
|
70
|
+
subscriptionId: config.subscriptionId,
|
|
71
|
+
callBack: config.handler,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DefaultContext, GenericEvent } from "../types.ts";
|
|
2
|
+
import { createEffectInMemoryEventQueries } from "./EffectInMemoryEventQueries.ts";
|
|
3
|
+
import {
|
|
4
|
+
createEffectInMemoryEventRepository,
|
|
5
|
+
createEffectInMemoryWithUow,
|
|
6
|
+
} from "./EffectInMemoryEventRepository.ts";
|
|
7
|
+
|
|
8
|
+
export type { GetEventsParams } from "../ports/EventQueries.ts";
|
|
9
|
+
export * from "./EffectEventCrawler.ts";
|
|
10
|
+
export * from "./EffectInMemoryEventBus.ts";
|
|
11
|
+
export * from "./EffectInMemoryEventQueries.ts";
|
|
12
|
+
export * from "./EffectInMemoryEventRepository.ts";
|
|
13
|
+
export * from "./EffectSubscriptions.ts";
|
|
14
|
+
export type * from "./ports/EffectEventBus.ts";
|
|
15
|
+
export type * from "./ports/EffectEventQueries.ts";
|
|
16
|
+
export type * from "./ports/EffectEventRepository.ts";
|
|
17
|
+
|
|
18
|
+
export const createEffectInMemoryEventRepositoryAndQueries = <
|
|
19
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
20
|
+
>() => {
|
|
21
|
+
const { eventRepository, helpers } =
|
|
22
|
+
createEffectInMemoryEventRepository<Event>();
|
|
23
|
+
const { eventQueries } = createEffectInMemoryEventQueries<Event>(helpers);
|
|
24
|
+
const { withUow } = createEffectInMemoryWithUow<Event>(eventRepository);
|
|
25
|
+
return { eventRepository, eventQueries, helpers, withUow };
|
|
26
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
import type {
|
|
3
|
+
DefaultContext,
|
|
4
|
+
GenericEvent,
|
|
5
|
+
SubscriptionId,
|
|
6
|
+
} from "../../types.ts";
|
|
7
|
+
|
|
8
|
+
export type EventBus<
|
|
9
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
10
|
+
> = {
|
|
11
|
+
publish: (event: Event) => Effect.Effect<void>;
|
|
12
|
+
subscribe: <E extends GenericEvent<string, unknown, DefaultContext>>(params: {
|
|
13
|
+
topic: E["topic"];
|
|
14
|
+
subscriptionId: SubscriptionId;
|
|
15
|
+
callBack: (e: E) => Effect.Effect<void>;
|
|
16
|
+
}) => void;
|
|
17
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
import type { GetEventsParams } from "../../ports/EventQueries.ts";
|
|
3
|
+
import type { DefaultContext, GenericEvent } from "../../types.ts";
|
|
4
|
+
|
|
5
|
+
export type EventQueries<
|
|
6
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
7
|
+
> = {
|
|
8
|
+
getEvents: (params: GetEventsParams) => Effect.Effect<Event[]>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
import type { DefaultContext, GenericEvent } from "../../types.ts";
|
|
3
|
+
|
|
4
|
+
export type EventRepository<
|
|
5
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
6
|
+
> = {
|
|
7
|
+
save: (event: Event) => Effect.Effect<void>;
|
|
8
|
+
saveNewEventsBatch: (events: Event[]) => Effect.Effect<void>;
|
|
9
|
+
markEventsAsInProcess: (events: Event[]) => Effect.Effect<void>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type EventsUnitOfWork<
|
|
13
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
14
|
+
> = {
|
|
15
|
+
eventRepository: EventRepository<Event>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type WithEventsUowOptions = {
|
|
19
|
+
afterCommit?: () => Effect.Effect<void>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type WithEventsUow<
|
|
23
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
24
|
+
> = <T>(
|
|
25
|
+
fn: (uow: EventsUnitOfWork<Event>) => Effect.Effect<T>,
|
|
26
|
+
options?: WithEventsUowOptions,
|
|
27
|
+
) => Effect.Effect<T>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GetEventsParams } from "./ports/EventQueries.ts";
|
|
2
|
+
import type { DefaultContext, GenericEvent } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
export const filterEvents = <
|
|
5
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
6
|
+
>(
|
|
7
|
+
events: Event[],
|
|
8
|
+
{ filters, limit }: GetEventsParams,
|
|
9
|
+
): Event[] => {
|
|
10
|
+
const matchesContext = (event: Event): boolean => {
|
|
11
|
+
if (!filters.context) return true;
|
|
12
|
+
if (!event.context) return false;
|
|
13
|
+
|
|
14
|
+
return Object.entries(filters.context).every(
|
|
15
|
+
([key, value]) => event.context?.[key] === value,
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const matchesOccurredAt = (event: Event): boolean => {
|
|
20
|
+
if (!filters.occurredAt) return true;
|
|
21
|
+
|
|
22
|
+
const { from, to } = filters.occurredAt;
|
|
23
|
+
const eventTime = event.occurredAt.getTime();
|
|
24
|
+
|
|
25
|
+
if (from && eventTime < from.getTime()) return false;
|
|
26
|
+
if (to && eventTime > to.getTime()) return false;
|
|
27
|
+
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return events
|
|
32
|
+
.filter(
|
|
33
|
+
(event) =>
|
|
34
|
+
filters.statuses.includes(event.status) &&
|
|
35
|
+
matchesContext(event) &&
|
|
36
|
+
matchesOccurredAt(event),
|
|
37
|
+
)
|
|
38
|
+
.slice(0, limit);
|
|
39
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DefaultContext, GenericEvent } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export const getSubscriptionIdsToPublish = <
|
|
4
|
+
Event extends GenericEvent<string, unknown, DefaultContext>,
|
|
5
|
+
>(
|
|
6
|
+
event: Event,
|
|
7
|
+
allSubscriptionIds: string[],
|
|
8
|
+
): string[] => {
|
|
9
|
+
if (event.publications.length === 0 || event.status === "to-republish") {
|
|
10
|
+
return allSubscriptionIds;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const lastPublication = event.publications.reduce((latest, current) =>
|
|
14
|
+
current.publishedAt > latest.publishedAt ? current : latest,
|
|
15
|
+
);
|
|
16
|
+
const failedSubscriptionIds = (lastPublication.failures ?? []).map(
|
|
17
|
+
(failure) => failure.subscriptionId,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return allSubscriptionIds.filter((id) => failedSubscriptionIds.includes(id));
|
|
21
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DefaultContext, EventStatus, GenericEvent } from "../types.ts";
|
|
2
2
|
|
|
3
3
|
/** Parameters for querying events. */
|
|
4
|
-
type GetEventsParams = {
|
|
4
|
+
export type GetEventsParams = {
|
|
5
5
|
filters: {
|
|
6
6
|
/** Filter by event status (e.g., ["never-published", "failed-but-will-retry"]). */
|
|
7
7
|
statuses: EventStatus[];
|