1/**
2 * Typed event definitions for the unstable event-log system.
3 *
4 * An `Event` is the durable contract shared by writers, handlers, journals, and
5 * replicas. It gives an event a stable tag, derives the aggregate or entity
6 * primary key from the decoded payload, and records the schemas used for the
7 * payload, handler result, and handler errors. The payload schema is also used
8 * to derive the SchemaBinary encoding for journal entries and remote
9 * replication.
10 *
11 * @stability unstable
12 * @since 4.0.0
13 */
14import * as SchemaBinary from "../encoding/SchemaBinary.ts"
15import { pipeArguments } from "../Pipeable.ts"
16import * as Predicate from "../Predicate.ts"
17import * as Schema from "../Schema.ts"
18
19/**
20 * Unique type identifier used to mark event log event definitions.
21 *
22 * @stability unstable
23 * @category type IDs
24 * @since 4.0.0
25 */
26export type TypeId = "~effect/eventlog/Event"
27
28/**
29 * Runtime type identifier used to mark event log event definitions.
30 *
31 * @stability unstable
32 * @category type IDs
33 * @since 4.0.0
34 */
35export const TypeId: TypeId = "~effect/eventlog/Event"
36
37/**
38 * Returns `true` when a value is an event log event definition.
39 *
40 * @stability unstable
41 * @category guards
42 * @since 4.0.0
43 */
44export const isEvent = (u: unknown): u is Event<any, any, any, any> => Predicate.hasProperty(u, TypeId)
45
46/**
47 * Definition of an event type that can be written to an `EventLog`.
48 *
49 * **Details**
50 *
51 * An event definition contains its tag, primary-key function, payload schema,
52 * SchemaBinary payload codec, success schema, and error schema.
53 *
54 * @stability unstable
55 * @category models
56 * @since 4.0.0
57 */
58export interface Event<
59 out Tag extends string,
60 in out Payload extends Schema.Top = typeof Schema.Void,
61 in out Success extends Schema.Top = typeof Schema.Void,
62 in out Error extends Schema.Top = typeof Schema.Never
63> {
64 readonly [TypeId]: TypeId
65 readonly tag: Tag
66 readonly primaryKey: (payload: Schema.Schema.Type<Payload>) => string
67 readonly payload: Payload
68 readonly payloadSchemaBinary: SchemaBinary.toCodec<Payload>
69 readonly success: Success
70 readonly error: Error
71}
72
73/**
74 * Marker service associated with the handler for an event tag.
75 *
76 * **Details**
77 *
78 * `ToService` derives this service from an `Event` so handler layers can expose
79 * which events they implement.
80 *
81 * @stability unstable
82 * @category models
83 * @since 4.0.0
84 */
85export interface EventHandler<in out Tag extends string> {
86 readonly _: unique symbol
87 readonly tag: Tag
88}
89
90/**
91 * Type-erased event log event definition.
92 *
93 * **Details**
94 *
95 * It preserves the runtime tag, primary-key function, payload schema, success
96 * schema, and error schema without retaining the original type parameters.
97 *
98 * @stability unstable
99 * @category models
100 * @since 4.0.0
101 */
102export interface Any {
103 readonly [TypeId]: TypeId
104 readonly tag: string
105 readonly primaryKey: (payload: any) => string
106 readonly payload: Schema.Top
107 readonly payloadSchemaBinary: SchemaBinary.toCodec<Schema.Top>
108 readonly success: Schema.Top
109 readonly error: Schema.Top
110}
111
112/**
113 * Type-erased event definition with its runtime properties available
114 * structurally.
115 *
116 * @stability unstable
117 * @category models
118 * @since 4.0.0
119 */
120export interface AnyWithProps extends Any {}
121
122/**
123 * Derives the handler service marker for an event definition.
124 *
125 * @stability unstable
126 * @category utility types
127 * @since 4.0.0
128 */
129export type ToService<A> = A extends Event<
130 infer _Tag,
131 infer _Payload,
132 infer _Success,
133 infer _Error
134> ? EventHandler<_Tag> :
135 never
136
137/**
138 * Extracts the tag string from an event definition.
139 *
140 * @stability unstable
141 * @category utility types
142 * @since 4.0.0
143 */
144export type Tag<A> = A extends Event<
145 infer _Tag,
146 infer _Payload,
147 infer _Success,
148 infer _Error
149> ? _Tag :
150 never
151
152/**
153 * Extracts the error schema from an event definition.
154 *
155 * @stability unstable
156 * @category utility types
157 * @since 4.0.0
158 */
159export type ErrorSchema<A extends Any> = A extends Event<
160 infer _Tag,
161 infer _Payload,
162 infer _Success,
163 infer _Error
164> ? _Error
165 : never
166
167/**
168 * Decoded error value type for an event definition.
169 *
170 * @stability unstable
171 * @category utility types
172 * @since 4.0.0
173 */
174export type Error<A extends Any> = Schema.Schema.Type<ErrorSchema<A>>
175
176/**
177 * Returns an event definition type whose error schema also includes the provided
178 * error schema.
179 *
180 * @stability unstable
181 * @category utility types
182 * @since 4.0.0
183 */
184export type AddError<A extends Any, Error extends Schema.Top> = A extends Event<
185 infer _Tag,
186 infer _Payload,
187 infer _Success,
188 infer _Error
189> ? Event<_Tag, _Payload, _Success, _Error | Error>
190 : never
191
192/**
193 * Extracts the payload schema from an event definition.
194 *
195 * @stability unstable
196 * @category utility types
197 * @since 4.0.0
198 */
199export type PayloadSchema<A extends Any> = A extends Event<
200 infer _Tag,
201 infer _Payload,
202 infer _Success,
203 infer _Error
204> ? _Payload
205 : never
206
207/**
208 * Extracts the payload schema for the event in a union with the specified tag.
209 *
210 * @stability unstable
211 * @category utility types
212 * @since 4.0.0
213 */
214export type PayloadSchemaWithTag<A extends Any, Tag extends string> = A extends Event<
215 Tag,
216 infer _Payload,
217 infer _Success,
218 infer _Error
219> ? _Payload
220 : never
221
222/**
223 * Decoded payload value type for an event definition.
224 *
225 * @stability unstable
226 * @category utility types
227 * @since 4.0.0
228 */
229export type Payload<A extends Any> = Schema.Schema.Type<PayloadSchema<A>>
230
231/**
232 * Tagged payload value for an event definition.
233 *
234 * **Details**
235 *
236 * The result contains `_tag` set to the event tag and `payload` set to the
237 * decoded payload value.
238 *
239 * @stability unstable
240 * @category utility types
241 * @since 4.0.0
242 */
243export type TaggedPayload<A extends Any> = A extends Event<
244 infer _Tag,
245 infer _Payload,
246 infer _Success,
247 infer _Error
248> ? {
249 readonly _tag: _Tag
250 readonly payload: Schema.Schema.Type<_Payload>
251 }
252 : never
253
254/**
255 * Extracts the success schema from an event definition.
256 *
257 * @stability unstable
258 * @category utility types
259 * @since 4.0.0
260 */
261export type SuccessSchema<A extends Any> = A extends Event<
262 infer _Tag,
263 infer _Payload,
264 infer _Success,
265 infer _Error
266> ? _Success
267 : never
268
269/**
270 * Decoded success value type for an event definition.
271 *
272 * @stability unstable
273 * @category utility types
274 * @since 4.0.0
275 */
276export type Success<A extends Any> = Schema.Schema.Type<SuccessSchema<A>>
277
278/**
279 * Schema services required by a client for an event definition.
280 *
281 * **Details**
282 *
283 * This includes payload encoding services plus success and error decoding
284 * services.
285 *
286 * @stability unstable
287 * @category utility types
288 * @since 4.0.0
289 */
290export type ServicesClient<A> = A extends Event<
291 infer _Tag,
292 infer _Payload,
293 infer _Success,
294 infer _Error
295> ?
296 | _Payload["EncodingServices"]
297 | _Success["DecodingServices"]
298 | _Error["DecodingServices"]
299 : never
300
301/**
302 * Schema services required by a server for an event definition.
303 *
304 * **Details**
305 *
306 * This includes payload decoding services plus success and error encoding
307 * services.
308 *
309 * @stability unstable
310 * @category utility types
311 * @since 4.0.0
312 */
313export type ServicesServer<A> = A extends Event<
314 infer _Tag,
315 infer _Payload,
316 infer _Success,
317 infer _Error
318> ?
319 | _Payload["DecodingServices"]
320 | _Success["EncodingServices"]
321 | _Error["EncodingServices"]
322 : never
323
324/**
325 * All schema services required to encode and decode the payload, success, and
326 * error schemas for an event definition.
327 *
328 * @stability unstable
329 * @category utility types
330 * @since 4.0.0
331 */
332export type Services<A> = A extends Event<
333 infer _Tag,
334 infer _Payload,
335 infer _Success,
336 infer _Error
337> ?
338 | _Payload["DecodingServices"]
339 | _Success["EncodingServices"]
340 | _Error["EncodingServices"]
341 | _Payload["EncodingServices"]
342 | _Success["DecodingServices"]
343 | _Error["DecodingServices"]
344 : never
345
346/**
347 * Extracts the event definition with the specified tag from an event union.
348 *
349 * @stability unstable
350 * @category utility types
351 * @since 4.0.0
352 */
353export type WithTag<Events extends Any, Tag extends string> = Extract<Events, { readonly tag: Tag }>
354
355/**
356 * Removes event definitions with the specified tag from an event union.
357 *
358 * @stability unstable
359 * @category utility types
360 * @since 4.0.0
361 */
362export type ExcludeTag<Events extends Any, Tag extends string> = Exclude<Events, { readonly tag: Tag }>
363
364/**
365 * Decoded payload value type for the event in a union with the specified tag.
366 *
367 * @stability unstable
368 * @category utility types
369 * @since 4.0.0
370 */
371export type PayloadWithTag<Events extends Any, Tag extends string> = Payload<WithTag<Events, Tag>>
372
373/**
374 * Decoded success value type for the event in a union with the specified tag.
375 *
376 * @stability unstable
377 * @category utility types
378 * @since 4.0.0
379 */
380export type SuccessWithTag<Events extends Any, Tag extends string> = Success<WithTag<Events, Tag>>
381
382/**
383 * Decoded error value type for the event in a union with the specified tag.
384 *
385 * @stability unstable
386 * @category utility types
387 * @since 4.0.0
388 */
389export type ErrorWithTag<Events extends Any, Tag extends string> = Error<WithTag<Events, Tag>>
390
391/**
392 * Client-side schema services required for the event in a union with the specified
393 * tag.
394 *
395 * @stability unstable
396 * @category utility types
397 * @since 4.0.0
398 */
399export type ServicesClientWithTag<Events extends Any, Tag extends string> = ServicesClient<WithTag<Events, Tag>>
400
401const Proto = {
402 [TypeId]: TypeId,
403 pipe() {
404 return pipeArguments(this, arguments)
405 }
406}
407
408/**
409 * Creates an event log event definition.
410 *
411 * **Details**
412 *
413 * If omitted, the payload and success schemas default to `Schema.Void`, the error
414 * schema defaults to `Schema.Never`, and the SchemaBinary payload codec is derived
415 * from the payload schema.
416 *
417 * @stability unstable
418 * @category constructors
419 * @since 4.0.0
420 */
421export function make<
422 Tag extends string,
423 Payload extends Schema.Top = typeof Schema.Void,
424 Success extends Schema.Top = typeof Schema.Void,
425 Error extends Schema.Top = typeof Schema.Never
426>(options: {
427 readonly tag: Tag
428 readonly primaryKey: (payload: Schema.Schema.Type<Payload>) => string
429 readonly payload?: Payload | undefined
430 readonly success?: Success | undefined
431 readonly error?: Error | undefined
432}): Event<Tag, Payload, Success, Error>
433/**
434 * @stability unstable
435 */
436export function make(options: {
437 readonly tag: string
438 readonly primaryKey: (payload: Schema.Schema.Type<Schema.Top>) => string
439 readonly payload?: Schema.Constraint | undefined
440 readonly success?: Schema.Constraint | undefined
441 readonly error?: Schema.Constraint | undefined
442}): Event<string, Schema.Top, Schema.Top, typeof Schema.Never> {
443 const payload = options.payload ?? Schema.Void
444 const success = options.success ?? Schema.Void
445 const error = options.error ?? Schema.Never
446 return Object.assign(Object.create(Proto), {
447 tag: options.tag,
448 primaryKey: options.primaryKey,
449 payload,
450 payloadSchemaBinary: SchemaBinary.toCodec(payload),
451 success,
452 error
453 })
454}
455
456/**
457 * Adds another error schema to an event definition.
458 *
459 * **Details**
460 *
461 * The returned event keeps the same tag, primary key, payload, and success schema
462 * while replacing the error schema with a union of the existing and new errors.
463 *
464 * @stability unstable
465 * @category constructors
466 * @since 4.0.0
467 */
468export function addError<A extends Any, Error2 extends Schema.Top>(
469 event: A,
470 error: Error2
471): AddError<A, Error2>
472/**
473 * @stability unstable
474 */
475export function addError(event: Any, error: Schema.Top): Any {
476 return make({
477 tag: event.tag,
478 primaryKey: event.primaryKey,
479 payload: event.payload,
480 success: event.success,
481 error: Schema.Union([event.error, error])
482 })
483}