1/**
2 * Defines groups of typed events for the unstable event-log system.
3 *
4 * An `EventGroup` collects event definitions that belong together. Start with
5 * `empty`, add events with their payload, success, and error schemas, then use
6 * the group to build clients with `EventLog.schema` and server handlers with
7 * `EventLog.group`. The group only describes events; it does not write or run
8 * them.
9 *
10 * @stability unstable
11 * @since 4.0.0
12 */
13import { type Pipeable, pipeArguments } from "../Pipeable.ts"
14import * as Predicate from "../Predicate.ts"
15import * as Record from "../Record.ts"
16import type * as Schema from "../Schema.ts"
17import * as Event from "./Event.ts"
18
19/**
20 * Unique type identifier used to mark event log event groups.
21 *
22 * @stability unstable
23 * @category type IDs
24 * @since 4.0.0
25 */
26export type TypeId = "~effect/eventlog/EventGroup"
27
28/**
29 * Runtime type identifier used to mark event log event groups.
30 *
31 * @stability unstable
32 * @category type IDs
33 * @since 4.0.0
34 */
35export const TypeId: TypeId = "~effect/eventlog/EventGroup"
36
37/**
38 * Returns `true` when a value is an event log event group.
39 *
40 * @stability unstable
41 * @category guards
42 * @since 4.0.0
43 */
44export const isEventGroup = (u: unknown): u is Any => Predicate.hasProperty(u, TypeId)
45
46/**
47 * Typed collection of event definitions that represents a portion of an event log
48 * domain.
49 *
50 * **When to use**
51 *
52 * Use when build groups from `empty.add(...)`, then provide implementations for the events
53 * with `EventLog.group`.
54 *
55 * @stability unstable
56 * @category models
57 * @since 4.0.0
58 */
59export interface EventGroup<
60 out Events extends Event.Any = Event.Any
61> extends Pipeable {
62 readonly [TypeId]: TypeId
63 readonly events: Record.ReadonlyRecord<string, Events>
64
65 /**
66 * Add an `Event` to the `EventGroup`.
67 */
68 add<
69 Tag extends string,
70 Payload extends Schema.Top = typeof Schema.Void,
71 Success extends Schema.Top = typeof Schema.Void,
72 Error extends Schema.Top = typeof Schema.Never
73 >(options: {
74 readonly tag: Tag
75 readonly primaryKey: (payload: Schema.Schema.Type<Payload>) => string
76 readonly payload?: Payload
77 readonly success?: Success
78 readonly error?: Error
79 }): EventGroup<Events | Event.Event<Tag, Payload, Success, Error>>
80
81 /**
82 * Add an error schema to all the events in the `EventGroup`.
83 */
84 addError<Error extends Schema.Top>(error: Error): EventGroup<Event.AddError<Events, Error>>
85}
86
87/**
88 * Type-erased marker for an event log event group.
89 *
90 * @stability unstable
91 * @category models
92 * @since 4.0.0
93 */
94export interface Any {
95 readonly [TypeId]: TypeId
96}
97
98/**
99 * Type-erased event group with its events record available structurally.
100 *
101 * @stability unstable
102 * @category models
103 * @since 4.0.0
104 */
105export type AnyWithProps = EventGroup<Event.Any>
106
107/**
108 * Derives the handler service markers required for all events in an event group.
109 *
110 * @stability unstable
111 * @category utility types
112 * @since 4.0.0
113 */
114export type ToService<A> = A extends EventGroup<infer _Events> ? Event.ToService<_Events>
115 : never
116
117/**
118 * Extracts the union of event definitions contained in an event group.
119 *
120 * @stability unstable
121 * @category utility types
122 * @since 4.0.0
123 */
124export type Events<Group> = Group extends EventGroup<infer _Events> ? _Events
125 : never
126
127/**
128 * Client-side schema services required by all events in an event group.
129 *
130 * @stability unstable
131 * @category utility types
132 * @since 4.0.0
133 */
134export type ServicesClient<Group> = Event.ServicesClient<Events<Group>>
135
136/**
137 * Server-side schema services required by all events in an event group.
138 *
139 * @stability unstable
140 * @category utility types
141 * @since 4.0.0
142 */
143export type ServicesServer<Group> = Event.ServicesServer<Events<Group>>
144
145const makeProto = <
146 Events extends Event.Any
147>(options: {
148 readonly events: Record.ReadonlyRecord<string, Events>
149}): EventGroup<Events> => {
150 const EventGroupClass = (_: never) => {}
151 const group = Object.assign(EventGroupClass, {
152 [TypeId]: TypeId,
153 events: options.events,
154 add<
155 Tag extends string,
156 Payload extends Schema.Top = typeof Schema.Void,
157 Success extends Schema.Top = typeof Schema.Void,
158 Error extends Schema.Top = typeof Schema.Never
159 >(
160 this: EventGroup<Events>,
161 addOptions: {
162 readonly tag: Tag
163 readonly primaryKey: (payload: Schema.Schema.Type<Payload>) => string
164 readonly payload?: Payload
165 readonly success?: Success
166 readonly error?: Error
167 }
168 ): EventGroup<Events | Event.Event<Tag, Payload, Success, Error>> {
169 return makeProto({
170 events: {
171 ...this.events,
172 [addOptions.tag]: Event.make(addOptions)
173 }
174 })
175 },
176 addError<Error extends Schema.Top>(
177 this: EventGroup<Events>,
178 error: Error
179 ): EventGroup<Event.AddError<Events, Error>> {
180 const events = Record.map<string, Events, Event.AddError<Events, Error>>(
181 this.events,
182 (event) => Event.addError(event, error)
183 )
184 return makeProto({ events })
185 },
186 pipe() {
187 return pipeArguments(this, arguments)
188 }
189 })
190 return group
191}
192
193/**
194 * Creates an empty event group used as the starting point for defining a group.
195 *
196 * **When to use**
197 *
198 * Use when you need the starting `EventGroup` value before adding event
199 * definitions with `.add(...)`.
200 *
201 * @stability unstable
202 * @category constructors
203 * @since 4.0.0
204 */
205export const empty: EventGroup<never> = makeProto({ events: Record.empty() })