@l-etabli/events 0.5.0 → 0.6.1

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 (62) hide show
  1. package/README.md +146 -155
  2. package/dist/adapters/in-memory/InMemoryEventBus.cjs +8 -3
  3. package/dist/adapters/in-memory/InMemoryEventBus.cjs.map +1 -1
  4. package/dist/adapters/in-memory/InMemoryEventBus.d.cts +10 -2
  5. package/dist/adapters/in-memory/InMemoryEventBus.d.ts +10 -2
  6. package/dist/adapters/in-memory/InMemoryEventBus.mjs +11 -4
  7. package/dist/adapters/in-memory/InMemoryEventBus.mjs.map +1 -1
  8. package/dist/adapters/in-memory/index.d.cts +1 -0
  9. package/dist/adapters/in-memory/index.d.ts +1 -0
  10. package/dist/adapters/kysely/KyselyEventQueries.cjs +27 -22
  11. package/dist/adapters/kysely/KyselyEventQueries.cjs.map +1 -1
  12. package/dist/adapters/kysely/KyselyEventQueries.d.cts +1 -1
  13. package/dist/adapters/kysely/KyselyEventQueries.d.ts +1 -1
  14. package/dist/adapters/kysely/KyselyEventQueries.mjs +27 -22
  15. package/dist/adapters/kysely/KyselyEventQueries.mjs.map +1 -1
  16. package/dist/adapters/kysely/KyselyEventRepository.cjs +43 -36
  17. package/dist/adapters/kysely/KyselyEventRepository.cjs.map +1 -1
  18. package/dist/adapters/kysely/KyselyEventRepository.d.cts +1 -1
  19. package/dist/adapters/kysely/KyselyEventRepository.d.ts +1 -1
  20. package/dist/adapters/kysely/KyselyEventRepository.mjs +43 -36
  21. package/dist/adapters/kysely/KyselyEventRepository.mjs.map +1 -1
  22. package/dist/adapters/kysely/migration.cjs +1 -1
  23. package/dist/adapters/kysely/migration.cjs.map +1 -1
  24. package/dist/adapters/kysely/migration.mjs +1 -1
  25. package/dist/adapters/kysely/migration.mjs.map +1 -1
  26. package/dist/adapters/kysely/types.cjs.map +1 -1
  27. package/dist/adapters/kysely/types.d.cts +7 -3
  28. package/dist/adapters/kysely/types.d.ts +7 -3
  29. package/dist/createNewEvent.cjs +19 -12
  30. package/dist/createNewEvent.cjs.map +1 -1
  31. package/dist/createNewEvent.d.cts +43 -39
  32. package/dist/createNewEvent.d.ts +43 -39
  33. package/dist/createNewEvent.mjs +19 -12
  34. package/dist/createNewEvent.mjs.map +1 -1
  35. package/dist/eventDefinitions.cjs +32 -0
  36. package/dist/eventDefinitions.cjs.map +1 -0
  37. package/dist/eventDefinitions.d.cts +20 -0
  38. package/dist/eventDefinitions.d.ts +20 -0
  39. package/dist/eventDefinitions.mjs +7 -0
  40. package/dist/eventDefinitions.mjs.map +1 -0
  41. package/dist/index.cjs +5 -1
  42. package/dist/index.cjs.map +1 -1
  43. package/dist/index.d.cts +3 -2
  44. package/dist/index.d.ts +3 -2
  45. package/dist/index.mjs +2 -0
  46. package/dist/index.mjs.map +1 -1
  47. package/dist/types.cjs +37 -0
  48. package/dist/types.cjs.map +1 -1
  49. package/dist/types.d.cts +42 -3
  50. package/dist/types.d.ts +42 -3
  51. package/dist/types.mjs +25 -0
  52. package/dist/types.mjs.map +1 -1
  53. package/package.json +2 -2
  54. package/src/adapters/in-memory/InMemoryEventBus.ts +63 -9
  55. package/src/adapters/kysely/KyselyEventQueries.ts +35 -29
  56. package/src/adapters/kysely/KyselyEventRepository.ts +65 -57
  57. package/src/adapters/kysely/migration.ts +3 -1
  58. package/src/adapters/kysely/types.ts +7 -2
  59. package/src/createNewEvent.ts +116 -48
  60. package/src/eventDefinitions.ts +53 -0
  61. package/src/index.ts +2 -1
  62. package/src/types.ts +74 -2
@@ -1,87 +1,155 @@
1
- import type { DefaultContext, EventId, GenericEvent, UserId } from "./types.ts";
1
+ import type {
2
+ EventDefinitions,
3
+ InferEventsFromDefinitions,
4
+ } from "./eventDefinitions.ts";
5
+ import type { Actor, DefaultContext, EventId, GenericEvent } from "./types.ts";
2
6
 
3
7
  type MakeCreateNewEventOptions = {
4
8
  getNow?: () => Date;
5
9
  generateId?: () => EventId;
6
10
  };
7
11
 
8
- type ContextParam<
12
+ type MakeCreateNewEventFromDefinitionsOptions<
13
+ Definitions extends EventDefinitions,
14
+ > = MakeCreateNewEventOptions & {
15
+ eventDefinitions: Definitions;
16
+ };
17
+
18
+ type DeclaredContextForEvent<
19
+ Event extends GenericEvent<string, unknown, DefaultContext>,
20
+ Topic extends Event["topic"],
21
+ > = Exclude<Extract<Event, { topic: Topic }>["context"], undefined>;
22
+
23
+ type ContextParamForEvent<
9
24
  Event extends GenericEvent<string, unknown, DefaultContext>,
10
25
  Topic extends Event["topic"],
11
- > = Extract<Event, { topic: Topic }>["context"] extends undefined
26
+ > = [DeclaredContextForEvent<Event, Topic>] extends [never]
12
27
  ? { context?: undefined }
13
- : { context: Extract<Event, { topic: Topic }>["context"] };
28
+ : { context: DeclaredContextForEvent<Event, Topic> };
29
+
30
+ type BaseCreateNewEventParams = {
31
+ triggeredByActor: Actor;
32
+ id?: EventId;
33
+ occurredAt?: Date;
34
+ flowId?: string;
35
+ causedByEventId?: EventId;
36
+ };
14
37
 
15
38
  type CreateNewEventParams<
16
39
  Event extends GenericEvent<string, unknown, DefaultContext>,
17
40
  Topic extends Event["topic"],
18
- > = {
41
+ > = BaseCreateNewEventParams & {
19
42
  topic: Topic;
20
43
  payload: Extract<Event, { topic: Topic }>["payload"];
21
- triggeredByUserId: UserId;
22
- id?: EventId;
23
- occurredAt?: Date;
24
44
  priority?: number;
25
- } & ContextParam<Event, Topic>;
45
+ } & ContextParamForEvent<Event, Topic>;
46
+
47
+ type PayloadFromDefinitions<
48
+ Definitions extends EventDefinitions,
49
+ Topic extends keyof Definitions & string,
50
+ > = Definitions[Topic] extends {
51
+ _payload?: infer Payload;
52
+ }
53
+ ? Payload
54
+ : never;
55
+
56
+ type DeclaredContextFromDefinitions<
57
+ Definitions extends EventDefinitions,
58
+ Topic extends keyof Definitions & string,
59
+ > = Exclude<
60
+ Definitions[Topic] extends { _context?: infer Context } ? Context : never,
61
+ undefined
62
+ >;
63
+
64
+ type ContextParamFromDefinitions<
65
+ Definitions extends EventDefinitions,
66
+ Topic extends keyof Definitions & string,
67
+ > = [DeclaredContextFromDefinitions<Definitions, Topic>] extends [never]
68
+ ? { context?: undefined }
69
+ : { context: DeclaredContextFromDefinitions<Definitions, Topic> };
70
+
71
+ type CreateNewEventParamsFromDefinitions<
72
+ Definitions extends EventDefinitions,
73
+ Topic extends keyof Definitions & string,
74
+ > = BaseCreateNewEventParams & {
75
+ topic: Topic;
76
+ payload: PayloadFromDefinitions<Definitions, Topic>;
77
+ } & ContextParamFromDefinitions<Definitions, Topic>;
26
78
 
27
79
  /**
28
80
  * Creates a typed event creator factory for your event union.
29
81
  * Provides type-safe event creation where topic constrains payload type.
30
- *
31
- * @param options.getNow - Function to get current time (default: `() => new Date()`)
32
- * @param options.generateId - Function to generate event IDs (default: `() => crypto.randomUUID()`)
33
- *
34
- * @example
35
- * ```typescript
36
- * type MyEvents =
37
- * | GenericEvent<"UserCreated", { email: string }>
38
- * | GenericEvent<"OrderPlaced", { orderId: string }>;
39
- *
40
- * // Standalone usage:
41
- * const createEvent = makeCreateNewEvent<MyEvents>();
42
- *
43
- * // Or get it from createInMemoryEventBus (recommended):
44
- * const { eventBus, createEvent } = createInMemoryEventBus<MyEvents>(withUow);
45
- *
46
- * // Type-safe: payload must match topic
47
- * createEvent({ topic: "UserCreated", payload: { email: "a@b.com" }, triggeredByUserId: "u1" }); // OK
48
- * createEvent({ topic: "UserCreated", payload: { orderId: "123" }, triggeredByUserId: "u1" }); // Error!
49
- *
50
- * // For testing, inject deterministic functions:
51
- * const createEvent = makeCreateNewEvent<MyEvents>({
52
- * getNow: () => new Date("2024-01-01"),
53
- * generateId: () => "test-id",
54
- * });
55
- * ```
56
82
  */
57
-
58
83
  export type CreateNewEvent<
59
84
  Event extends GenericEvent<string, unknown, DefaultContext>,
60
85
  > = <Topic extends Event["topic"]>(
61
86
  params: CreateNewEventParams<Event, Topic>,
62
87
  ) => Extract<Event, { topic: Topic }>;
63
88
 
64
- export const makeCreateNewEvent = <
89
+ /**
90
+ * Creates a typed event creator factory directly from event definitions.
91
+ * Priority is injected from the canonical topic definition.
92
+ */
93
+ export type CreateNewEventFromDefinitions<
94
+ Definitions extends EventDefinitions,
95
+ > = <Topic extends keyof Definitions & string>(
96
+ params: CreateNewEventParamsFromDefinitions<Definitions, Topic>,
97
+ ) => Extract<InferEventsFromDefinitions<Definitions>, { topic: Topic }>;
98
+
99
+ export function makeCreateNewEvent<
65
100
  Event extends GenericEvent<string, unknown, DefaultContext>,
101
+ >(options?: MakeCreateNewEventOptions): CreateNewEvent<Event>;
102
+ export function makeCreateNewEvent<Definitions extends EventDefinitions>(
103
+ options: MakeCreateNewEventFromDefinitionsOptions<Definitions>,
104
+ ): CreateNewEventFromDefinitions<Definitions>;
105
+ export function makeCreateNewEvent<
106
+ Event extends GenericEvent<string, unknown, DefaultContext>,
107
+ Definitions extends EventDefinitions,
66
108
  >(
67
- options: MakeCreateNewEventOptions = {},
68
- ): CreateNewEvent<Event> => {
109
+ options:
110
+ | MakeCreateNewEventOptions
111
+ | MakeCreateNewEventFromDefinitionsOptions<Definitions> = {},
112
+ ): CreateNewEvent<Event> | CreateNewEventFromDefinitions<Definitions> {
69
113
  const getNow = options.getNow ?? (() => new Date());
70
114
  const generateId =
71
115
  options.generateId ?? (() => crypto.randomUUID() as EventId);
116
+ const eventDefinitions =
117
+ "eventDefinitions" in options ? options.eventDefinitions : undefined;
118
+
119
+ return ((params: {
120
+ topic: string;
121
+ payload: unknown;
122
+ triggeredByActor: Actor;
123
+ id?: EventId;
124
+ occurredAt?: Date;
125
+ flowId?: string;
126
+ causedByEventId?: EventId;
127
+ context?: DefaultContext;
128
+ priority?: number;
129
+ }) => {
130
+ const definitionPriority = eventDefinitions?.[params.topic]?.priority;
131
+ const priority =
132
+ definitionPriority ??
133
+ ("priority" in params
134
+ ? (params.priority as number | undefined)
135
+ : undefined);
72
136
 
73
- return <Topic extends Event["topic"]>(
74
- params: CreateNewEventParams<Event, Topic>,
75
- ): Extract<Event, { topic: Topic }> =>
76
- ({
137
+ return {
77
138
  id: params.id ?? generateId(),
78
139
  topic: params.topic,
79
140
  payload: params.payload,
80
- triggeredByUserId: params.triggeredByUserId,
141
+ triggeredByActor: params.triggeredByActor,
81
142
  occurredAt: params.occurredAt ?? getNow(),
82
143
  status: "never-published",
83
144
  publications: [],
84
- priority: params.priority,
85
- context: params.context,
86
- }) as unknown as Extract<Event, { topic: Topic }>;
87
- };
145
+ ...(priority !== undefined ? { priority } : {}),
146
+ ...(params.context !== undefined ? { context: params.context } : {}),
147
+ ...(params.flowId !== undefined ? { flowId: params.flowId } : {}),
148
+ ...(params.causedByEventId !== undefined
149
+ ? { causedByEventId: params.causedByEventId }
150
+ : {}),
151
+ };
152
+ }) as unknown as
153
+ | CreateNewEvent<Event>
154
+ | CreateNewEventFromDefinitions<Definitions>;
155
+ }
@@ -0,0 +1,53 @@
1
+ import type { DefaultContext, GenericEvent } from "./types.ts";
2
+
3
+ export type EventDefinition<
4
+ Payload,
5
+ Context extends DefaultContext = undefined,
6
+ > = {
7
+ priority?: number;
8
+ _payload?: Payload;
9
+ _context?: Context;
10
+ };
11
+
12
+ export type EventDefinitions = Record<
13
+ string,
14
+ EventDefinition<unknown, DefaultContext>
15
+ >;
16
+
17
+ type PayloadFromDefinition<Definition> = Definition extends EventDefinition<
18
+ infer Payload,
19
+ DefaultContext
20
+ >
21
+ ? Payload
22
+ : never;
23
+
24
+ type ContextFromDefinition<Definition> = Definition extends EventDefinition<
25
+ unknown,
26
+ infer Context
27
+ >
28
+ ? Context
29
+ : never;
30
+
31
+ export type InferEventsFromDefinitions<Definitions extends EventDefinitions> = {
32
+ [Topic in keyof Definitions & string]: GenericEvent<
33
+ Topic,
34
+ PayloadFromDefinition<Definitions[Topic]>,
35
+ ContextFromDefinition<Definitions[Topic]>
36
+ >;
37
+ }[keyof Definitions & string];
38
+
39
+ type DefineEventOptions = {
40
+ priority?: number;
41
+ };
42
+
43
+ export const defineEvent = <
44
+ Payload,
45
+ Context extends DefaultContext = undefined,
46
+ >(
47
+ options: DefineEventOptions = {},
48
+ ): EventDefinition<Payload, Context> =>
49
+ options as EventDefinition<Payload, Context>;
50
+
51
+ export const defineEvents = <Definitions extends EventDefinitions>(
52
+ definitions: Definitions,
53
+ ): Definitions => definitions;
package/src/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export * from "./adapters/in-memory/index.ts";
2
2
  export * from "./createEventCrawler.ts";
3
3
  export * from "./createNewEvent.ts";
4
+ export * from "./eventDefinitions.ts";
4
5
  export type * from "./ports/EventBus.ts";
5
6
  export type * from "./ports/EventQueries.ts";
6
7
  export type * from "./ports/EventRepository.ts";
7
8
  export * from "./subscriptions.ts";
8
- export type * from "./types.ts";
9
+ export * from "./types.ts";
package/src/types.ts CHANGED
@@ -12,6 +12,74 @@ export type SubscriptionId = Flavor<string, "SubscriptionId">;
12
12
  /** Unique identifier for a user who triggered an event. */
13
13
  export type UserId = Flavor<string, "UserId">;
14
14
 
15
+ /** Actor representing a user action. */
16
+ export type UserActor<Id extends string = string> = {
17
+ kind: "user";
18
+ id: Id;
19
+ };
20
+
21
+ /** Actor representing platform or infrastructure initiated work. */
22
+ export type SystemActor = {
23
+ kind: "system";
24
+ };
25
+
26
+ /** Actor representing a background worker or job. */
27
+ export type WorkerActor<Id extends string = string> = {
28
+ kind: "worker";
29
+ id?: Id;
30
+ };
31
+
32
+ /** Actor representing an API key. */
33
+ export type ApiKeyActor<Id extends string = string> = {
34
+ kind: "api-key";
35
+ id: Id;
36
+ };
37
+
38
+ /** Actor representing an anonymous caller. */
39
+ export type AnonymousActor = {
40
+ kind: "anonymous";
41
+ };
42
+
43
+ /** JSON-serializable actor metadata persisted alongside the event. */
44
+ export type Actor =
45
+ | UserActor
46
+ | SystemActor
47
+ | WorkerActor
48
+ | ApiKeyActor
49
+ | AnonymousActor;
50
+
51
+ /** Creates a typed user actor. */
52
+ export const createUserActor = <Id extends string>(id: Id): UserActor<Id> => ({
53
+ kind: "user",
54
+ id,
55
+ });
56
+
57
+ /** Creates a typed system actor. */
58
+ export const createSystemActor = (): SystemActor => ({
59
+ kind: "system",
60
+ });
61
+
62
+ /** Creates a typed worker actor. */
63
+ export const createWorkerActor = <Id extends string>(
64
+ id?: Id,
65
+ ): WorkerActor<Id> => ({
66
+ kind: "worker",
67
+ ...(id !== undefined ? { id } : {}),
68
+ });
69
+
70
+ /** Creates a typed API key actor. */
71
+ export const createApiKeyActor = <Id extends string>(
72
+ id: Id,
73
+ ): ApiKeyActor<Id> => ({
74
+ kind: "api-key",
75
+ id,
76
+ });
77
+
78
+ /** Creates a typed anonymous actor. */
79
+ export const createAnonymousActor = (): AnonymousActor => ({
80
+ kind: "anonymous",
81
+ });
82
+
15
83
  /** Unique identifier for an event. */
16
84
  export type EventId = Flavor<string, "EventId">;
17
85
 
@@ -91,8 +159,12 @@ export type GenericEvent<
91
159
  status: EventStatus;
92
160
  /** History of publication attempts. */
93
161
  publications: EventPublication[];
94
- /** User who triggered the action that created this event. */
95
- triggeredByUserId: UserId;
162
+ /** Actor identifier that triggered the action that created this event. */
163
+ triggeredByActor: Actor;
164
+ /** Optional correlation identifier for a request or cross-event flow. */
165
+ flowId?: string;
166
+ /** Optional parent event identifier for cascading events. */
167
+ causedByEventId?: EventId;
96
168
  /** Optional priority for processing order (not yet implemented in crawler). */
97
169
  priority?: number;
98
170
  /** Optional context for filtering events (e.g., by tenant). */