1/**
2 * @title Creating effects from common sources
3 *
4 * Learn how to create effects from various sources, including plain values,
5 * synchronous code, Promise APIs, optional values, and callback-based APIs.
6 */
7import { Effect, Schema } from "effect"
8
9class InvalidPayload extends Schema.TaggedError<InvalidPayload>()("InvalidPayload", {
10 input: Schema.String,
11 cause: Schema.Defect()
12}) {}
13
14class UserLookupError extends Schema.TaggedError<UserLookupError>()("UserLookupError", {
15 userId: Schema.Int,
16 cause: Schema.Defect()
17}) {}
18
19class MissingWorkspaceId extends Schema.TaggedError<MissingWorkspaceId>()("MissingWorkspaceId", {}) {}
20
21// Some request fields are optional and may be absent.
22const requestHeaders = new Map<string, string>([
23 ["x-request-id", "req_1"]
24])
25
26// `Effect.succeed` wraps values you already have in memory.
27export const fromValue = Effect.succeed({ env: "prod", retries: 3 })
28
29// `Effect.sync` wraps synchronous side effects that should not throw.
30export const fromSyncSideEffect = Effect.sync(() => Date.now())
31
32// `Effect.try` wraps synchronous code that may throw.
33export const parsePayload = Effect.fn("parsePayload")((input: string) =>
34 Effect.try({
35 try: () => JSON.parse(input) as { readonly userId: number },
36 catch: (cause) => new InvalidPayload({ input, cause })
37 })
38)
39
40const users = new Map<number, { readonly id: number; readonly name: string }>([
41 [1, { id: 1, name: "Ada" }],
42 [2, { id: 2, name: "Lin" }]
43])
44
45// `Effect.tryPromise` wraps Promise-based APIs that can reject or throw.
46export const fetchUser = Effect.fn("fetchUser")((userId: number) =>
47 Effect.tryPromise({
48 async try() {
49 const user = users.get(userId)
50 if (!user) {
51 throw new Error(`Missing user ${userId}`)
52 }
53 return user
54 },
55 catch: (cause) => new UserLookupError({ userId, cause })
56 })
57)
58
59// `Effect.fromNullishOr` turns nullable values into a typed effect.
60export const fromNullishHeader = Effect.fromNullishOr(requestHeaders.get("x-workspace-id")).pipe(
61 Effect.mapError(() => new MissingWorkspaceId())
62)
63
64// `Effect.callback` wraps callback-style asynchronous APIs.
65export const fromCallback = Effect.callback<number>((resume) => {
66 const timeoutId = setTimeout(() => {
67 resume(Effect.succeed(200))
68 }, 10)
69
70 // Return a finalizer so interruption can cancel the callback source.
71 return Effect.sync(() => {
72 clearTimeout(timeoutId)
73 })
74})