Skip to content

Batching

In typical application development, when interacting with external APIs, databases, or other data sources, we often define functions that perform requests and handle their results or failures accordingly.

Here’s a basic model that outlines the structure of our data and possible errors:

1
// ------------------------------
2
// Model
3
// ------------------------------
4
5
interface
interface User
User
{
6
readonly
(property) User._tag: "User"
_tag
: "User"
7
readonly
(property) User.id: number
id
: number
8
readonly
(property) User.name: string
name
: string
9
readonly
(property) User.email: string
email
: string
10
}
11
12
class
class GetUserError
GetUserError
{
13
readonly
(property) GetUserError._tag: "GetUserError"
_tag
= "GetUserError"
14
}
15
16
interface
interface Todo
Todo
{
17
readonly
(property) Todo._tag: "Todo"
_tag
: "Todo"
18
readonly
(property) Todo.id: number
id
: number
19
readonly
(property) Todo.message: string
message
: string
20
readonly
(property) Todo.ownerId: number
ownerId
: number
21
}
22
23
class
class GetTodosError
GetTodosError
{
24
readonly
(property) GetTodosError._tag: "GetTodosError"
_tag
= "GetTodosError"
25
}
26
27
class
class SendEmailError
SendEmailError
{
28
readonly
(property) SendEmailError._tag: "SendEmailError"
_tag
= "SendEmailError"
29
}

Let’s define functions that interact with an external API, handling common operations such as fetching todos, retrieving user details, and sending emails.

1
import {
import Effect
Effect
} from "effect"
2
3
// ------------------------------
4
// Model
5
// ------------------------------
6
25 collapsed lines
7
interface
interface User
User
{
8
readonly
(property) User._tag: "User"
_tag
: "User"
9
readonly
(property) User.id: number
id
: number
10
readonly
(property) User.name: string
name
: string
11
readonly
(property) User.email: string
email
: string
12
}
13
14
class
class GetUserError
GetUserError
{
15
readonly
(property) GetUserError._tag: "GetUserError"
_tag
= "GetUserError"
16
}
17
18
interface
interface Todo
Todo
{
19
readonly
(property) Todo._tag: "Todo"
_tag
: "Todo"
20
readonly
(property) Todo.id: number
id
: number
21
readonly
(property) Todo.message: string
message
: string
22
readonly
(property) Todo.ownerId: number
ownerId
: number
23
}
24
25
class
class GetTodosError
GetTodosError
{
26
readonly
(property) GetTodosError._tag: "GetTodosError"
_tag
= "GetTodosError"
27
}
28
29
class
class SendEmailError
SendEmailError
{
30
readonly
(property) SendEmailError._tag: "SendEmailError"
_tag
= "SendEmailError"
31
}
32
33
// ------------------------------
34
// API
35
// ------------------------------
36
37
// Fetches a list of todos from an external API
38
const
const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
=
import Effect
Effect
.
const tryPromise: <Todo[], GetTodosError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>; readonly catch: (error: unknown) => GetTodosError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
39
(property) try: (signal: AbortSignal) => PromiseLike<Todo[]>
try
: () =>
40
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/todos").
(method) Promise<Response>.then<Todo[], never>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
41
(
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>
42
),
43
(property) catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError(): GetTodosError
GetTodosError
()
44
})
45
46
// Retrieves a user by their ID from an external API
47
const
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
= (
(parameter) id: number
id
: number) =>
48
import Effect
Effect
.
const tryPromise: <User, GetUserError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<User>; readonly catch: (error: unknown) => GetUserError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
49
(property) try: (signal: AbortSignal) => PromiseLike<User>
try
: () =>
50
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
(`https://api.example.demo/getUserById?id=${
(parameter) id: number
id
}`).
(method) Promise<Response>.then<User, never>(onfulfilled?: ((value: Response) => User | PromiseLike<User>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
51
(
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface User
User
>
52
),
53
(property) catch: (error: unknown) => GetUserError
catch
: () => new
constructor GetUserError(): GetUserError
GetUserError
()
54
})
55
56
// Sends an email via an external API
57
const
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
= (
(parameter) address: string
address
: string,
(parameter) text: string
text
: string) =>
58
import Effect
Effect
.
const tryPromise: <void, SendEmailError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<void>; readonly catch: (error: unknown) => SendEmailError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
59
(property) try: (signal: AbortSignal) => PromiseLike<void>
try
: () =>
60
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/sendEmail", {
61
(property) RequestInit.method?: string
method
: "POST",
62
(property) RequestInit.headers?: HeadersInit
headers
: {
63
"Content-Type": "application/json"
64
},
65
(property) RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
(method) JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

stringify
({
(property) address: string
address
,
(property) text: string
text
})
66
}).
(method) Promise<Response>.then<void, never>(onfulfilled?: ((value: Response) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void>),
67
(property) catch: (error: unknown) => SendEmailError
catch
: () => new
constructor SendEmailError(): SendEmailError
SendEmailError
()
68
})
69
70
// Sends an email to a user by fetching their details first
71
const
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
= (
(parameter) id: number
id
: number,
(parameter) message: string
message
: string) =>
72
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
(parameter) id: number
id
).
(method) Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
73
import Effect
Effect
.
const andThen: <User, Effect.Effect<void, SendEmailError, never>>(f: (a: User) => Effect.Effect<void, SendEmailError, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) user: User
user
) =>
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
(
(parameter) user: User
user
.
(property) User.email: string
email
,
(parameter) message: string
message
))
74
)
75
76
// Notifies the owner of a todo by sending them an email
77
const
const notifyOwner: (todo: Todo) => Effect.Effect<void, GetUserError | SendEmailError, never>
notifyOwner
= (
(parameter) todo: Todo
todo
:
interface Todo
Todo
) =>
78
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
(parameter) todo: Todo
todo
.
(property) Todo.ownerId: number
ownerId
).
(method) Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
79
import Effect
Effect
.
const andThen: <User, Effect.Effect<void, GetUserError | SendEmailError, never>>(f: (a: User) => Effect.Effect<void, GetUserError | SendEmailError, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) user: User
user
) =>
80
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
(
(parameter) user: User
user
.
(property) User.id: number
id
, `hey ${
(parameter) user: User
user
.
(property) User.name: string
name
} you got a todo!`)
81
)
82
)

While this approach is straightforward and readable, it may not be the most efficient. Repeated API calls, especially when many todos share the same owner, can significantly increase network overhead and slow down your application.

While these functions are clear and easy to understand, their use may not be the most efficient. For example, notifying todo owners involves repeated API calls which can be optimized.

1
import {
import Effect
Effect
} from "effect"
2
3
// ------------------------------
4
// Model
5
// ------------------------------
6
25 collapsed lines
7
interface
interface User
User
{
8
readonly
(property) User._tag: "User"
_tag
: "User"
9
readonly
(property) User.id: number
id
: number
10
readonly
(property) User.name: string
name
: string
11
readonly
(property) User.email: string
email
: string
12
}
13
14
class
class GetUserError
GetUserError
{
15
readonly
(property) GetUserError._tag: "GetUserError"
_tag
= "GetUserError"
16
}
17
18
interface
interface Todo
Todo
{
19
readonly
(property) Todo._tag: "Todo"
_tag
: "Todo"
20
readonly
(property) Todo.id: number
id
: number
21
readonly
(property) Todo.message: string
message
: string
22
readonly
(property) Todo.ownerId: number
ownerId
: number
23
}
24
25
class
class GetTodosError
GetTodosError
{
26
readonly
(property) GetTodosError._tag: "GetTodosError"
_tag
= "GetTodosError"
27
}
28
29
class
class SendEmailError
SendEmailError
{
30
readonly
(property) SendEmailError._tag: "SendEmailError"
_tag
= "SendEmailError"
31
}
32
33
// ------------------------------
34
// API
35
// ------------------------------
36
46 collapsed lines
37
// Fetches a list of todos from an external API
38
const
const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
=
import Effect
Effect
.
const tryPromise: <Todo[], GetTodosError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>; readonly catch: (error: unknown) => GetTodosError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
39
(property) try: (signal: AbortSignal) => PromiseLike<Todo[]>
try
: () =>
40
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/todos").
(method) Promise<Response>.then<Todo[], never>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
41
(
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>
42
),
43
(property) catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError(): GetTodosError
GetTodosError
()
44
})
45
46
// Retrieves a user by their ID from an external API
47
const
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
= (
(parameter) id: number
id
: number) =>
48
import Effect
Effect
.
const tryPromise: <User, GetUserError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<User>; readonly catch: (error: unknown) => GetUserError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
49
(property) try: (signal: AbortSignal) => PromiseLike<User>
try
: () =>
50
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
(`https://api.example.demo/getUserById?id=${
(parameter) id: number
id
}`).
(method) Promise<Response>.then<User, never>(onfulfilled?: ((value: Response) => User | PromiseLike<User>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
51
(
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface User
User
>
52
),
53
(property) catch: (error: unknown) => GetUserError
catch
: () => new
constructor GetUserError(): GetUserError
GetUserError
()
54
})
55
56
// Sends an email via an external API
57
const
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
= (
(parameter) address: string
address
: string,
(parameter) text: string
text
: string) =>
58
import Effect
Effect
.
const tryPromise: <void, SendEmailError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<void>; readonly catch: (error: unknown) => SendEmailError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
59
(property) try: (signal: AbortSignal) => PromiseLike<void>
try
: () =>
60
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/sendEmail", {
61
(property) RequestInit.method?: string
method
: "POST",
62
(property) RequestInit.headers?: HeadersInit
headers
: {
63
"Content-Type": "application/json"
64
},
65
(property) RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
(method) JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

stringify
({
(property) address: string
address
,
(property) text: string
text
})
66
}).
(method) Promise<Response>.then<void, never>(onfulfilled?: ((value: Response) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void>),
67
(property) catch: (error: unknown) => SendEmailError
catch
: () => new
constructor SendEmailError(): SendEmailError
SendEmailError
()
68
})
69
70
// Sends an email to a user by fetching their details first
71
const
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
= (
(parameter) id: number
id
: number,
(parameter) message: string
message
: string) =>
72
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
(parameter) id: number
id
).
(method) Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
73
import Effect
Effect
.
const andThen: <User, Effect.Effect<void, SendEmailError, never>>(f: (a: User) => Effect.Effect<void, SendEmailError, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) user: User
user
) =>
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
(
(parameter) user: User
user
.
(property) User.email: string
email
,
(parameter) message: string
message
))
74
)
75
76
// Notifies the owner of a todo by sending them an email
77
const
const notifyOwner: (todo: Todo) => Effect.Effect<void, GetUserError | SendEmailError, never>
notifyOwner
= (
(parameter) todo: Todo
todo
:
interface Todo
Todo
) =>
78
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
(parameter) todo: Todo
todo
.
(property) Todo.ownerId: number
ownerId
).
(method) Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
79
import Effect
Effect
.
const andThen: <User, Effect.Effect<void, GetUserError | SendEmailError, never>>(f: (a: User) => Effect.Effect<void, GetUserError | SendEmailError, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) user: User
user
) =>
80
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
(
(parameter) user: User
user
.
(property) User.id: number
id
, `hey ${
(parameter) user: User
user
.
(property) User.name: string
name
} you got a todo!`)
81
)
82
)
83
84
// Orchestrates operations on todos, notifying their owners
85
const
const program: Effect.Effect<void, GetUserError | GetTodosError | SendEmailError, never>
program
=
import Effect
Effect
.
const gen: <YieldWrap<Effect.Effect<Todo[], GetTodosError, never>> | YieldWrap<Effect.Effect<void[], GetUserError | SendEmailError, never>>, void>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)
gen
(function* () {
86
const
const todos: Todo[]
todos
= yield*
const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
87
yield*
import Effect
Effect
.
const forEach: <void, GetUserError | SendEmailError, never, Todo[]>(self: Todo[], f: (a: Todo, i: number) => Effect.Effect<void, GetUserError | SendEmailError, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
const todos: Todo[]
todos
, (
(parameter) todo: Todo
todo
) =>
const notifyOwner: (todo: Todo) => Effect.Effect<void, GetUserError | SendEmailError, never>
notifyOwner
(
(parameter) todo: Todo
todo
), {
88
(property) concurrency?: Concurrency | undefined
concurrency
: "unbounded"
89
})
90
})

This implementation performs an API call for each todo to fetch the owner’s details and send an email. If multiple todos have the same owner, this results in redundant API calls.

Let’s assume that getUserById and sendEmail can be batched. This means that we can send multiple requests in a single HTTP call, reducing the number of API requests and improving performance.

Step-by-Step Guide to Batching

  1. Declaring Requests: We’ll start by transforming our requests into structured data models. This involves detailing input parameters, expected outputs, and possible errors. Structuring requests this way not only helps in efficiently managing data but also in comparing different requests to understand if they refer to the same input parameters.

  2. Declaring Resolvers: Resolvers are designed to handle multiple requests simultaneously. By leveraging the ability to compare requests (ensuring they refer to the same input parameters), resolvers can execute several requests in one go, maximizing the utility of batching.

  3. Defining Queries: Finally, we’ll define queries that utilize these batch-resolvers to perform operations. This step ties together the structured requests and their corresponding resolvers into functional components of the application.

We’ll design a model using the concept of a Request that a data source might support:

Request<Value, Error>

A Request is a construct representing a request for a value of type Value, which might fail with an error of type Error.

Let’s start by defining a structured model for the types of requests our data sources can handle.

1
import {
import Request
Request
} from "effect"
2
3
// ------------------------------
4
// Model
5
// ------------------------------
6
25 collapsed lines
7
interface
interface User
User
{
8
readonly
(property) User._tag: "User"
_tag
: "User"
9
readonly
(property) User.id: number
id
: number
10
readonly
(property) User.name: string
name
: string
11
readonly
(property) User.email: string
email
: string
12
}
13
14
class
class GetUserError
GetUserError
{
15
readonly
(property) GetUserError._tag: "GetUserError"
_tag
= "GetUserError"
16
}
17
18
interface
interface Todo
Todo
{
19
readonly
(property) Todo._tag: "Todo"
_tag
: "Todo"
20
readonly
(property) Todo.id: number
id
: number
21
readonly
(property) Todo.message: string
message
: string
22
readonly
(property) Todo.ownerId: number
ownerId
: number
23
}
24
25
class
class GetTodosError
GetTodosError
{
26
readonly
(property) GetTodosError._tag: "GetTodosError"
_tag
= "GetTodosError"
27
}
28
29
class
class SendEmailError
SendEmailError
{
30
readonly
(property) SendEmailError._tag: "SendEmailError"
_tag
= "SendEmailError"
31
}
32
33
// ------------------------------
34
// Requests
35
// ------------------------------
36
37
// Define a request to get multiple Todo items which might
38
// fail with a GetTodosError
39
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
40
readonly
(property) GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
41
}
42
43
// Create a tagged constructor for GetTodos requests
44
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new `Request`.

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
45
46
// Define a request to fetch a User by ID which might
47
// fail with a GetUserError
48
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
49
readonly
(property) GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
50
readonly
(property) GetUserById.id: number
id
: number
51
}
52
53
// Create a tagged constructor for GetUserById requests
54
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new `Request`.

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
55
56
// Define a request to send an email which might
57
// fail with a SendEmailError
58
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<void,
class SendEmailError
SendEmailError
> {
59
readonly
(property) SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
60
readonly
(property) SendEmail.address: string
address
: string
61
readonly
(property) SendEmail.text: string
text
: string
62
}
63
64
// Create a tagged constructor for SendEmail requests
65
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new `Request`.

tagged
<
interface SendEmail
SendEmail
>("SendEmail")

Each request is defined with a specific data structure that extends from a generic Request type, ensuring that each request carries its unique data requirements along with a specific error type.

By using tagged constructors like Request.tagged, we can easily instantiate request objects that are recognizable and manageable throughout the application.

After defining our requests, the next step is configuring how Effect resolves these requests using RequestResolver:

RequestResolver<A, R>

A RequestResolver requires an environment R and is capable of executing requests of type A.

In this section, we’ll create individual resolvers for each type of request. The granularity of your resolvers can vary, but typically, they are divided based on the batching capabilities of the corresponding API calls.

1
import {
import Effect
Effect
,
import Request
Request
,
import RequestResolver
RequestResolver
} from "effect"
2
3
// ------------------------------
4
// Model
5
// ------------------------------
6
25 collapsed lines
7
interface
interface User
User
{
8
readonly
(property) User._tag: "User"
_tag
: "User"
9
readonly
(property) User.id: number
id
: number
10
readonly
(property) User.name: string
name
: string
11
readonly
(property) User.email: string
email
: string
12
}
13
14
class
class GetUserError
GetUserError
{
15
readonly
(property) GetUserError._tag: "GetUserError"
_tag
= "GetUserError"
16
}
17
18
interface
interface Todo
Todo
{
19
readonly
(property) Todo._tag: "Todo"
_tag
: "Todo"
20
readonly
(property) Todo.id: number
id
: number
21
readonly
(property) Todo.message: string
message
: string
22
readonly
(property) Todo.ownerId: number
ownerId
: number
23
}
24
25
class
class GetTodosError
GetTodosError
{
26
readonly
(property) GetTodosError._tag: "GetTodosError"
_tag
= "GetTodosError"
27
}
28
29
class
class SendEmailError
SendEmailError
{
30
readonly
(property) SendEmailError._tag: "SendEmailError"
_tag
= "SendEmailError"
31
}
32
33
// ------------------------------
34
// Requests
35
// ------------------------------
36
29 collapsed lines
37
// Define a request to get multiple Todo items which might
38
// fail with a GetTodosError
39
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
40
readonly
(property) GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
41
}
42
43
// Create a tagged constructor for GetTodos requests
44
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new `Request`.

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
45
46
// Define a request to fetch a User by ID which might
47
// fail with a GetUserError
48
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
49
readonly
(property) GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
50
readonly
(property) GetUserById.id: number
id
: number
51
}
52
53
// Create a tagged constructor for GetUserById requests
54
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new `Request`.

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
55
56
// Define a request to send an email which might
57
// fail with a SendEmailError
58
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<void,
class SendEmailError
SendEmailError
> {
59
readonly
(property) SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
60
readonly
(property) SendEmail.address: string
address
: string
61
readonly
(property) SendEmail.text: string
text
: string
62
}
63
64
// Create a tagged constructor for SendEmail requests
65
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new `Request`.

tagged
<
interface SendEmail
SendEmail
>("SendEmail")
66
67
// ------------------------------
68
// Resolvers
69
// ------------------------------
70
71
// Assuming GetTodos cannot be batched, we create a standard resolver
72
const
const GetTodosResolver: RequestResolver.RequestResolver<GetTodos, never>
GetTodosResolver
=
import RequestResolver
RequestResolver
.
const fromEffect: <never, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, never>) => RequestResolver.RequestResolver<GetTodos, never>

Constructs a data source from an effectual function.

fromEffect
(
73
(
(parameter) _: GetTodos
_
:
interface GetTodos
GetTodos
):
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
interface Todo
Todo
[],
class GetTodosError
GetTodosError
> =>
74
import Effect
Effect
.
const tryPromise: <Todo[], GetTodosError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>; readonly catch: (error: unknown) => GetTodosError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
75
(property) try: (signal: AbortSignal) => PromiseLike<Todo[]>
try
: () =>
76
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/todos").
(method) Promise<Response>.then<Todo[], Todo[]>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => Todo[] | PromiseLike<...>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
77
(
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>
78
),
79
(property) catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError(): GetTodosError
GetTodosError
()
80
})
81
)
82
83
// Assuming GetUserById can be batched, we create a batched resolver
84
const
const GetUserByIdResolver: RequestResolver.RequestResolver<GetUserById, never>
GetUserByIdResolver
=
import RequestResolver
RequestResolver
.
const makeBatched: <GetUserById, never>(run: (requests: [GetUserById, ...GetUserById[]]) => Effect.Effect<void, never, never>) => RequestResolver.RequestResolver<GetUserById, never>

Constructs a data source from a function taking a collection of requests.

makeBatched
(
85
(
(parameter) requests: readonly GetUserById[]
requests
:
interface ReadonlyArray<T>
ReadonlyArray
<
interface GetUserById
GetUserById
>) =>
86
import Effect
Effect
.
const tryPromise: <User[], GetUserError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<User[]>; readonly catch: (error: unknown) => GetUserError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
87
(property) try: (signal: AbortSignal) => PromiseLike<User[]>
try
: () =>
88
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/getUserByIdBatch", {
89
(property) RequestInit.method?: string
method
: "POST",
90
(property) RequestInit.headers?: HeadersInit
headers
: {
91
"Content-Type": "application/json"
92
},
93
(property) RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
(method) JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

stringify
({
94
(property) users: { id: number; }[]
users
:
(parameter) requests: readonly GetUserById[]
requests
.
(method) ReadonlyArray<GetUserById>.map<{ id: number; }>(callbackfn: (value: GetUserById, index: number, array: readonly GetUserById[]) => { id: number; }, thisArg?: any): { id: number; }[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

map
(({
(parameter) id: number
id
}) => ({
(property) id: number
id
}))
95
})
96
}).
(method) Promise<Response>.then<unknown, User[]>(onfulfilled?: ((value: Response) => unknown) | null | undefined, onrejected?: ((reason: any) => User[] | PromiseLike<User[]>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
()) as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface User
User
>>,
97
(property) catch: (error: unknown) => GetUserError
catch
: () => new
constructor GetUserError(): GetUserError
GetUserError
()
98
}).
(method) Pipeable.pipe<Effect.Effect<User[], GetUserError, never>, Effect.Effect<void[], GetUserError, never>, Effect.Effect<void[], never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
99
import Effect
Effect
.
const andThen: <User[], Effect.Effect<void[], never, never>>(f: (a: User[]) => Effect.Effect<void[], never, never>) => <E, R>(self: Effect.Effect<User[], E, R>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) users: User[]
users
) =>
100
import Effect
Effect
.
const forEach: <void, never, never, readonly GetUserById[]>(self: readonly GetUserById[], f: (a: GetUserById, i: number) => Effect.Effect<void, never, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
(parameter) requests: readonly GetUserById[]
requests
, (
(parameter) request: GetUserById
request
,
(parameter) index: number
index
) =>
101
import Request
Request
.
const completeEffect: <GetUserById, never>(self: GetUserById, effect: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a `Request` with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

completeEffect
(
(parameter) request: GetUserById
request
,
import Effect
Effect
.
const succeed: <User>(value: User) => Effect.Effect<User, never, never>
succeed
(
(parameter) users: User[]
users
[
(parameter) index: number
index
]!))
102
)
103
),
104
import Effect
Effect
.
const catchAll: <GetUserError, void[], never, never>(f: (e: GetUserError) => Effect.Effect<void[], never, never>) => <A, R>(self: Effect.Effect<A, GetUserError, R>) => Effect.Effect<...> (+1 overload)

Recovers from all recoverable errors. **Note**: that `Effect.catchAll` will not recover from unrecoverable defects. To recover from both recoverable and unrecoverable errors use `Effect.catchAllCause`.

catchAll
((
(parameter) error: GetUserError
error
) =>
105
import Effect
Effect
.
const forEach: <void, never, never, readonly GetUserById[]>(self: readonly GetUserById[], f: (a: GetUserById, i: number) => Effect.Effect<void, never, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
(parameter) requests: readonly GetUserById[]
requests
, (
(parameter) request: GetUserById
request
) =>
106
import Request
Request
.
const completeEffect: <GetUserById, never>(self: GetUserById, effect: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a `Request` with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

completeEffect
(
(parameter) request: GetUserById
request
,
import Effect
Effect
.
const fail: <GetUserError>(error: GetUserError) => Effect.Effect<never, GetUserError, never>
fail
(
(parameter) error: GetUserError
error
))
107
)
108
)
109
)
110
)
111
112
// Assuming SendEmail can be batched, we create a batched resolver
113
const
const SendEmailResolver: RequestResolver.RequestResolver<SendEmail, never>
SendEmailResolver
=
import RequestResolver
RequestResolver
.
const makeBatched: <SendEmail, never>(run: (requests: [SendEmail, ...SendEmail[]]) => Effect.Effect<void, never, never>) => RequestResolver.RequestResolver<SendEmail, never>

Constructs a data source from a function taking a collection of requests.

makeBatched
(
114
(
(parameter) requests: readonly SendEmail[]
requests
:
interface ReadonlyArray<T>
ReadonlyArray
<
interface SendEmail
SendEmail
>) =>
115
import Effect
Effect
.
const tryPromise: <void, SendEmailError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<void>; readonly catch: (error: unknown) => SendEmailError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
116
(property) try: (signal: AbortSignal) => PromiseLike<void>
try
: () =>
117
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/sendEmailBatch", {
118
(property) RequestInit.method?: string
method
: "POST",
119
(property) RequestInit.headers?: HeadersInit
headers
: {
120
"Content-Type": "application/json"
121
},
122
(property) RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
(method) JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

stringify
({
123
(property) emails: { address: string; text: string; }[]
emails
:
(parameter) requests: readonly SendEmail[]
requests
.
(method) ReadonlyArray<SendEmail>.map<{ address: string; text: string; }>(callbackfn: (value: SendEmail, index: number, array: readonly SendEmail[]) => { address: string; text: string; }, thisArg?: any): { address: string; text: string; }[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

map
(({
(parameter) address: string
address
,
(parameter) text: string
text
}) => ({
124
(property) address: string
address
,
125
(property) text: string
text
126
}))
127
})
128
}).
(method) Promise<Response>.then<void, never>(onfulfilled?: ((value: Response) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void>),
129
(property) catch: (error: unknown) => SendEmailError
catch
: () => new
constructor SendEmailError(): SendEmailError
SendEmailError
()
130
}).
(method) Pipeable.pipe<Effect.Effect<void, SendEmailError, never>, Effect.Effect<void[], SendEmailError, never>, Effect.Effect<void[], never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
131
import Effect
Effect
.
const andThen: <Effect.Effect<void[], never, never>>(f: Effect.Effect<void[], never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void[], E, R> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
(
132
import Effect
Effect
.
const forEach: <void, never, never, readonly SendEmail[]>(self: readonly SendEmail[], f: (a: SendEmail, i: number) => Effect.Effect<void, never, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
(parameter) requests: readonly SendEmail[]
requests
, (
(parameter) request: SendEmail
request
) =>
133
import Request
Request
.
const completeEffect: <SendEmail, never>(self: SendEmail, effect: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a `Request` with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

completeEffect
(
(parameter) request: SendEmail
request
,
import Effect
Effect
.
(alias) const void: Effect.Effect<void, never, never> export void
void
)
134
)
135
),
136
import Effect
Effect
.
const catchAll: <SendEmailError, void[], never, never>(f: (e: SendEmailError) => Effect.Effect<void[], never, never>) => <A, R>(self: Effect.Effect<A, SendEmailError, R>) => Effect.Effect<...> (+1 overload)

Recovers from all recoverable errors. **Note**: that `Effect.catchAll` will not recover from unrecoverable defects. To recover from both recoverable and unrecoverable errors use `Effect.catchAllCause`.

catchAll
((
(parameter) error: SendEmailError
error
) =>
137
import Effect
Effect
.
const forEach: <void, never, never, readonly SendEmail[]>(self: readonly SendEmail[], f: (a: SendEmail, i: number) => Effect.Effect<void, never, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
(parameter) requests: readonly SendEmail[]
requests
, (
(parameter) request: SendEmail
request
) =>
138
import Request
Request
.
const completeEffect: <SendEmail, never>(self: SendEmail, effect: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a `Request` with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

completeEffect
(
(parameter) request: SendEmail
request
,
import Effect
Effect
.
const fail: <SendEmailError>(error: SendEmailError) => Effect.Effect<never, SendEmailError, never>
fail
(
(parameter) error: SendEmailError
error
))
139
)
140
)
141
)
142
)

In this configuration:

  • GetTodosResolver handles the fetching of multiple Todo items. It’s set up as a standard resolver since we assume it cannot be batched.
  • GetUserByIdResolver and SendEmailResolver are configured as batched resolvers. This setup is based on the assumption that these requests can be processed in batches, enhancing performance and reducing the number of API calls.

Now that we’ve set up our resolvers, we’re ready to tie all the pieces together to define our This step will enable us to perform data operations effectively within our application.

1
import {
import Effect
Effect
,
import Request
Request
,
import RequestResolver
RequestResolver
} from "effect"
2
3
// ------------------------------
4
// Model
5
// ------------------------------
6
25 collapsed lines
7
interface
interface User
User
{
8
readonly
(property) User._tag: "User"
_tag
: "User"
9
readonly
(property) User.id: number
id
: number
10
readonly
(property) User.name: string
name
: string
11
readonly
(property) User.email: string
email
: string
12
}
13
14
class
class GetUserError
GetUserError
{
15
readonly
(property) GetUserError._tag: "GetUserError"
_tag
= "GetUserError"
16
}
17
18
interface
interface Todo
Todo
{
19
readonly
(property) Todo._tag: "Todo"
_tag
: "Todo"
20
readonly
(property) Todo.id: number
id
: number
21
readonly
(property) Todo.message: string
message
: string
22
readonly
(property) Todo.ownerId: number
ownerId
: number
23
}
24
25
class
class GetTodosError
GetTodosError
{
26
readonly
(property) GetTodosError._tag: "GetTodosError"
_tag
= "GetTodosError"
27
}
28
29
class
class SendEmailError
SendEmailError
{
30
readonly
(property) SendEmailError._tag: "SendEmailError"
_tag
= "SendEmailError"
31
}
32
33
// ------------------------------
34
// Requests
35
// ------------------------------
36
29 collapsed lines
37
// Define a request to get multiple Todo items which might
38
// fail with a GetTodosError
39
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
40
readonly
(property) GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
41
}
42
43
// Create a tagged constructor for GetTodos requests
44
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new `Request`.

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
45
46
// Define a request to fetch a User by ID which might
47
// fail with a GetUserError
48
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
49
readonly
(property) GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
50
readonly
(property) GetUserById.id: number
id
: number
51
}
52
53
// Create a tagged constructor for GetUserById requests
54
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new `Request`.

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
55
56
// Define a request to send an email which might
57
// fail with a SendEmailError
58
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<void,
class SendEmailError
SendEmailError
> {
59
readonly
(property) SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
60
readonly
(property) SendEmail.address: string
address
: string
61
readonly
(property) SendEmail.text: string
text
: string
62
}
63
64
// Create a tagged constructor for SendEmail requests
65
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new `Request`.

tagged
<
interface SendEmail
SendEmail
>("SendEmail")
66
67
// ------------------------------
68
// Resolvers
69
// ------------------------------
70
72 collapsed lines
71
// Assuming GetTodos cannot be batched, we create a standard resolver
72
const
const GetTodosResolver: RequestResolver.RequestResolver<GetTodos, never>
GetTodosResolver
=
import RequestResolver
RequestResolver
.
const fromEffect: <never, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, never>) => RequestResolver.RequestResolver<GetTodos, never>

Constructs a data source from an effectual function.

fromEffect
(
73
(
(parameter) _: GetTodos
_
:
interface GetTodos
GetTodos
):
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
interface Todo
Todo
[],
class GetTodosError
GetTodosError
> =>
74
import Effect
Effect
.
const tryPromise: <Todo[], GetTodosError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>; readonly catch: (error: unknown) => GetTodosError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
75
(property) try: (signal: AbortSignal) => PromiseLike<Todo[]>
try
: () =>
76
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/todos").
(method) Promise<Response>.then<Todo[], Todo[]>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => Todo[] | PromiseLike<...>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
77
(
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>
78
),
79
(property) catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError(): GetTodosError
GetTodosError
()
80
})
81
)
82
83
// Assuming GetUserById can be batched, we create a batched resolver
84
const
const GetUserByIdResolver: RequestResolver.RequestResolver<GetUserById, never>
GetUserByIdResolver
=
import RequestResolver
RequestResolver
.
const makeBatched: <GetUserById, never>(run: (requests: [GetUserById, ...GetUserById[]]) => Effect.Effect<void, never, never>) => RequestResolver.RequestResolver<GetUserById, never>

Constructs a data source from a function taking a collection of requests.

makeBatched
(
85
(
(parameter) requests: readonly GetUserById[]
requests
:
interface ReadonlyArray<T>
ReadonlyArray
<
interface GetUserById
GetUserById
>) =>
86
import Effect
Effect
.
const tryPromise: <User[], GetUserError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<User[]>; readonly catch: (error: unknown) => GetUserError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
87
(property) try: (signal: AbortSignal) => PromiseLike<User[]>
try
: () =>
88
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/getUserByIdBatch", {
89
(property) RequestInit.method?: string
method
: "POST",
90
(property) RequestInit.headers?: HeadersInit
headers
: {
91
"Content-Type": "application/json"
92
},
93
(property) RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
(method) JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

stringify
({
94
(property) users: { id: number; }[]
users
:
(parameter) requests: readonly GetUserById[]
requests
.
(method) ReadonlyArray<GetUserById>.map<{ id: number; }>(callbackfn: (value: GetUserById, index: number, array: readonly GetUserById[]) => { id: number; }, thisArg?: any): { id: number; }[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

map
(({
(parameter) id: number
id
}) => ({
(property) id: number
id
}))
95
})
96
}).
(method) Promise<Response>.then<unknown, User[]>(onfulfilled?: ((value: Response) => unknown) | null | undefined, onrejected?: ((reason: any) => User[] | PromiseLike<User[]>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
()) as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface User
User
>>,
97
(property) catch: (error: unknown) => GetUserError
catch
: () => new
constructor GetUserError(): GetUserError
GetUserError
()
98
}).
(method) Pipeable.pipe<Effect.Effect<User[], GetUserError, never>, Effect.Effect<void[], GetUserError, never>, Effect.Effect<void[], never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
99
import Effect
Effect
.
const andThen: <User[], Effect.Effect<void[], never, never>>(f: (a: User[]) => Effect.Effect<void[], never, never>) => <E, R>(self: Effect.Effect<User[], E, R>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) users: User[]
users
) =>
100
import Effect
Effect
.
const forEach: <void, never, never, readonly GetUserById[]>(self: readonly GetUserById[], f: (a: GetUserById, i: number) => Effect.Effect<void, never, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
(parameter) requests: readonly GetUserById[]
requests
, (
(parameter) request: GetUserById
request
,
(parameter) index: number
index
) =>
101
import Request
Request
.
const completeEffect: <GetUserById, never>(self: GetUserById, effect: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a `Request` with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

completeEffect
(
(parameter) request: GetUserById
request
,
import Effect
Effect
.
const succeed: <User>(value: User) => Effect.Effect<User, never, never>
succeed
(
(parameter) users: User[]
users
[
(parameter) index: number
index
]!))
102
)
103
),
104
import Effect
Effect
.
const catchAll: <GetUserError, void[], never, never>(f: (e: GetUserError) => Effect.Effect<void[], never, never>) => <A, R>(self: Effect.Effect<A, GetUserError, R>) => Effect.Effect<...> (+1 overload)

Recovers from all recoverable errors. **Note**: that `Effect.catchAll` will not recover from unrecoverable defects. To recover from both recoverable and unrecoverable errors use `Effect.catchAllCause`.

catchAll
((
(parameter) error: GetUserError
error
) =>
105
import Effect
Effect
.
const forEach: <void, never, never, readonly GetUserById[]>(self: readonly GetUserById[], f: (a: GetUserById, i: number) => Effect.Effect<void, never, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
(parameter) requests: readonly GetUserById[]
requests
, (
(parameter) request: GetUserById
request
) =>
106
import Request
Request
.
const completeEffect: <GetUserById, never>(self: GetUserById, effect: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a `Request` with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

completeEffect
(
(parameter) request: GetUserById
request
,
import Effect
Effect
.
const fail: <GetUserError>(error: GetUserError) => Effect.Effect<never, GetUserError, never>
fail
(
(parameter) error: GetUserError
error
))
107
)
108
)
109
)
110
)
111
112
// Assuming SendEmail can be batched, we create a batched resolver
113
const
const SendEmailResolver: RequestResolver.RequestResolver<SendEmail, never>
SendEmailResolver
=
import RequestResolver
RequestResolver
.
const makeBatched: <SendEmail, never>(run: (requests: [SendEmail, ...SendEmail[]]) => Effect.Effect<void, never, never>) => RequestResolver.RequestResolver<SendEmail, never>

Constructs a data source from a function taking a collection of requests.

makeBatched
(
114
(
(parameter) requests: readonly SendEmail[]
requests
:
interface ReadonlyArray<T>
ReadonlyArray
<
interface SendEmail
SendEmail
>) =>
115
import Effect
Effect
.
const tryPromise: <void, SendEmailError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<void>; readonly catch: (error: unknown) => SendEmailError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
116
(property) try: (signal: AbortSignal) => PromiseLike<void>
try
: () =>
117
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/sendEmailBatch", {
118
(property) RequestInit.method?: string
method
: "POST",
119
(property) RequestInit.headers?: HeadersInit
headers
: {
120
"Content-Type": "application/json"
121
},
122
(property) RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
(method) JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

stringify
({
123
(property) emails: { address: string; text: string; }[]
emails
:
(parameter) requests: readonly SendEmail[]
requests
.
(method) ReadonlyArray<SendEmail>.map<{ address: string; text: string; }>(callbackfn: (value: SendEmail, index: number, array: readonly SendEmail[]) => { address: string; text: string; }, thisArg?: any): { address: string; text: string; }[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

map
(({
(parameter) address: string
address
,
(parameter) text: string
text
}) => ({
124
(property) address: string
address
,
125
(property) text: string
text
126
}))
127
})
128
}).
(method) Promise<Response>.then<void, never>(onfulfilled?: ((value: Response) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void>),
129
(property) catch: (error: unknown) => SendEmailError
catch
: () => new
constructor SendEmailError(): SendEmailError
SendEmailError
()
130
}).
(method) Pipeable.pipe<Effect.Effect<void, SendEmailError, never>, Effect.Effect<void[], SendEmailError, never>, Effect.Effect<void[], never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
131
import Effect
Effect
.
const andThen: <Effect.Effect<void[], never, never>>(f: Effect.Effect<void[], never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void[], E, R> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
(
132
import Effect
Effect
.
const forEach: <void, never, never, readonly SendEmail[]>(self: readonly SendEmail[], f: (a: SendEmail, i: number) => Effect.Effect<void, never, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
(parameter) requests: readonly SendEmail[]
requests
, (
(parameter) request: SendEmail
request
) =>
133
import Request
Request
.
const completeEffect: <SendEmail, never>(self: SendEmail, effect: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a `Request` with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

completeEffect
(
(parameter) request: SendEmail
request
,
import Effect
Effect
.
(alias) const void: Effect.Effect<void, never, never> export void
void
)
134
)
135
),
136
import Effect
Effect
.
const catchAll: <SendEmailError, void[], never, never>(f: (e: SendEmailError) => Effect.Effect<void[], never, never>) => <A, R>(self: Effect.Effect<A, SendEmailError, R>) => Effect.Effect<...> (+1 overload)

Recovers from all recoverable errors. **Note**: that `Effect.catchAll` will not recover from unrecoverable defects. To recover from both recoverable and unrecoverable errors use `Effect.catchAllCause`.

catchAll
((
(parameter) error: SendEmailError
error
) =>
137
import Effect
Effect
.
const forEach: <void, never, never, readonly SendEmail[]>(self: readonly SendEmail[], f: (a: SendEmail, i: number) => Effect.Effect<void, never, never>, options?: { readonly concurrency?: Concurrency | undefined; readonly batching?: boolean | "inherit" | undefined; readonly discard?: false | undefined; readonly concurrentFinalizers?: boolean | undefined; } | undefined) => Effect.Effect<...> (+3 overloads)
forEach
(
(parameter) requests: readonly SendEmail[]
requests
, (
(parameter) request: SendEmail
request
) =>
138
import Request
Request
.
const completeEffect: <SendEmail, never>(self: SendEmail, effect: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a `Request` with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

completeEffect
(
(parameter) request: SendEmail
request
,
import Effect
Effect
.
const fail: <SendEmailError>(error: SendEmailError) => Effect.Effect<never, SendEmailError, never>
fail
(
(parameter) error: SendEmailError
error
))
139
)
140
)
141
)
142
)
143
144
// ------------------------------
145
// Queries
146
// ------------------------------
147
148
// Defines a query to fetch all Todo items
149
const
const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
:
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
150
interface Array<T>
Array
<
interface Todo
Todo
>,
151
class GetTodosError
GetTodosError
152
> =
import Effect
Effect
.
const request: <RequestResolver.RequestResolver<GetTodos, never>, GetTodos>(self: GetTodos, dataSource: RequestResolver.RequestResolver<GetTodos, never>) => Effect.Effect<...> (+1 overload)
request
(
const GetTodos: Request.Request<out A, out E = never>.Constructor (args: Omit<GetTodos, typeof Request.RequestTypeId | "_tag">) => GetTodos
GetTodos
({}),
const GetTodosResolver: RequestResolver.RequestResolver<GetTodos, never>
GetTodosResolver
)
153
154
// Defines a query to fetch a user by their ID
155
const
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
= (
(parameter) id: number
id
: number) =>
156
import Effect
Effect
.
const request: <RequestResolver.RequestResolver<GetUserById, never>, GetUserById>(self: GetUserById, dataSource: RequestResolver.RequestResolver<GetUserById, never>) => Effect.Effect<...> (+1 overload)
request
(
const GetUserById: Request.Request<out A, out E = never>.Constructor (args: Omit<GetUserById, typeof Request.RequestTypeId | "_tag">) => GetUserById
GetUserById
({
(property) id: number
id
}),
const GetUserByIdResolver: RequestResolver.RequestResolver<GetUserById, never>
GetUserByIdResolver
)
157
158
// Defines a query to send an email to a specific address
159
const
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
= (
(parameter) address: string
address
: string,
(parameter) text: string
text
: string) =>
160
import Effect
Effect
.
const request: <RequestResolver.RequestResolver<SendEmail, never>, SendEmail>(self: SendEmail, dataSource: RequestResolver.RequestResolver<SendEmail, never>) => Effect.Effect<...> (+1 overload)
request
(
const SendEmail: Request.Request<out A, out E = never>.Constructor (args: Omit<SendEmail, typeof Request.RequestTypeId | "_tag">) => SendEmail
SendEmail
({
(property) address: string
address
,
(property) text: string
text
}),
const SendEmailResolver: RequestResolver.RequestResolver<SendEmail, never>
SendEmailResolver
)
161
162
// Composes getUserById and sendEmail to send an email to a specific user
163
const
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
= (
(parameter) id: number
id
: number,
(parameter) message: string
message
: string) =>
164
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
(parameter) id: number
id
).
(method) Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
165
import Effect
Effect
.
const andThen: <User, Effect.Effect<void, SendEmailError, never>>(f: (a: User) => Effect.Effect<void, SendEmailError, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) user: User
user
) =>
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
(
(parameter) user: User
user
.
(property) User.email: string
email
,
(parameter) message: string
message
))
166
)
167
168
// Uses getUserById to fetch the owner of a Todo and then sends them an email notification
169
const
const notifyOwner: (todo: Todo) => Effect.Effect<void, GetUserError | SendEmailError, never>
notifyOwner
= (
(parameter) todo: Todo
todo
:
interface Todo
Todo
) =>
170
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
(parameter) todo: Todo
todo
.
(property) Todo.ownerId: number
ownerId
).
(method) Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
171
import Effect
Effect
.
const andThen: <User, Effect.Effect<void, GetUserError | SendEmailError, never>>(f: (a: User) => Effect.Effect<void, GetUserError | SendEmailError, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) user: User
user
) =>
172
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
(
(parameter) user: User
user
.
(property) User.id: number
id
, `hey ${
(parameter) user: User
user
.
(property) User.name: string
name
} you got a todo!`)
173
)
174
)

By using the Effect.request function, we integrate the resolvers with the request model effectively. This approach ensures that each query is optimally resolved using the appropriate resolver.

Although the code structure looks similar to earlier examples, employing resolvers significantly enhances efficiency by optimizing how requests are handled and reducing unnecessary API calls.

const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
batching: true
})
})

In the final setup, this program will execute only 3 queries to the APIs, regardless of the number of todos. This contrasts sharply with the traditional approach, which would potentially execute 1 + 2n queries, where n is the number of todos. This represents a significant improvement in efficiency, especially for applications with a high volume of data interactions.

Batching can be locally disabled using the Effect.withRequestBatching utility in the following way:

const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
concurrency: "unbounded"
})
}).pipe(Effect.withRequestBatching(false))

In complex applications, resolvers often need access to shared services or configurations to handle requests effectively. However, maintaining the ability to batch requests while providing the necessary context can be challenging. Here, we’ll explore how to manage context in resolvers to ensure that batching capabilities are not compromised.

When creating request resolvers, it’s crucial to manage the context carefully. Providing too much context or providing varying services to resolvers can make them incompatible for batching. To prevent such issues, the context for the resolver used in Effect.request is explicitly set to never. This forces developers to clearly define how the context is accessed and used within resolvers.

Consider the following example where we set up an HTTP service that the resolvers can use to execute API calls:

1
import {
import Effect
Effect
,
import Context
Context
,
import RequestResolver
RequestResolver
,
import Request
Request
} from "effect"
2
3
// ------------------------------
4
// Model
5
// ------------------------------
6
25 collapsed lines
7
interface
interface User
User
{
8
readonly
(property) User._tag: "User"
_tag
: "User"
9
readonly
(property) User.id: number
id
: number
10
readonly
(property) User.name: string
name
: string
11
readonly
(property) User.email: string
email
: string
12
}
13
14
class
class GetUserError
GetUserError
{
15
readonly
(property) GetUserError._tag: "GetUserError"
_tag
= "GetUserError"
16
}
17
18
interface
interface Todo
Todo
{
19
readonly
(property) Todo._tag: "Todo"
_tag
: "Todo"
20
readonly
(property) Todo.id: number
id
: number
21
readonly
(property) Todo.message: string
message
: string
22
readonly
(property) Todo.ownerId: number
ownerId
: number
23
}
24
25
class
class GetTodosError
GetTodosError
{
26
readonly
(property) GetTodosError._tag: "GetTodosError"
_tag
= "GetTodosError"
27
}
28
29
class
class SendEmailError
SendEmailError
{
30
readonly
(property) SendEmailError._tag: "SendEmailError"
_tag
= "SendEmailError"
31
}
32
33
// ------------------------------
34
// Requests
35
// ------------------------------
36
29 collapsed lines
37
// Define a request to get multiple Todo items which might
38
// fail with a GetTodosError
39
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
40
readonly
(property) GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
41
}
42
43
// Create a tagged constructor for GetTodos requests
44
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new `Request`.

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
45
46
// Define a request to fetch a User by ID which might
47
// fail with a GetUserError
48
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
49
readonly
(property) GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
50
readonly
(property) GetUserById.id: number
id
: number
51
}
52
53
// Create a tagged constructor for GetUserById requests
54
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new `Request`.

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
55
56
// Define a request to send an email which might
57
// fail with a SendEmailError
58
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<void,
class SendEmailError
SendEmailError
> {
59
readonly
(property) SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
60
readonly
(property) SendEmail.address: string
address
: string
61
readonly
(property) SendEmail.text: string
text
: string
62
}
63
64
// Create a tagged constructor for SendEmail requests
65
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new `Request`.

tagged
<
interface SendEmail
SendEmail
>("SendEmail")
66
67
// ------------------------------
68
// Resolvers With Context
69
// ------------------------------
70
71
class
class HttpService
HttpService
extends
import Context
Context
.
const Tag: <"HttpService">(id: "HttpService") => <Self, Shape>() => Context.TagClass<Self, "HttpService", Shape> namespace Tag
Tag
("HttpService")<
72
class HttpService
HttpService
,
73
{
(property) fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
: typeof
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
}
74
>() {}
75
76
const
const GetTodosResolver: Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>
GetTodosResolver
=
77
// we create a normal resolver like we did before
78
import RequestResolver
RequestResolver
.
const fromEffect: <HttpService, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, HttpService>) => RequestResolver.RequestResolver<...>

Constructs a data source from an effectual function.

fromEffect
((
(parameter) _: GetTodos
_
:
interface GetTodos
GetTodos
) =>
79
import Effect
Effect
.
const andThen: <{ fetch: typeof fetch; }, never, HttpService, Effect.Effect<Todo[], GetTodosError, never>>(self: Effect.Effect<{ fetch: typeof fetch; }, never, HttpService>, f: (a: { fetch: typeof fetch; }) => Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
(
class HttpService
HttpService
, (
(parameter) http: { fetch: typeof fetch; }
http
) =>
80
import Effect
Effect
.
const tryPromise: <Todo[], GetTodosError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>; readonly catch: (error: unknown) => GetTodosError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
81
(property) try: (signal: AbortSignal) => PromiseLike<Todo[]>
try
: () =>
82
(parameter) http: { fetch: typeof fetch; }
http
83
.
(property) fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
("https://api.example.demo/todos")
84
.
(method) Promise<Response>.then<Todo[], never>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>),
85
(property) catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError(): GetTodosError
GetTodosError
()
86
})
87
)
88
).
(method) Pipeable.pipe<RequestResolver.RequestResolver<GetTodos, HttpService>, Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>>(this: RequestResolver.RequestResolver<...>, ab: (_: RequestResolver.RequestResolver<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
89
// we list the tags that the resolver can access
90
import RequestResolver
RequestResolver
.
const contextFromServices: <[typeof HttpService]>(services_0: typeof HttpService) => <R, A>(self: RequestResolver.RequestResolver<A, R>) => Effect.Effect<RequestResolver.RequestResolver<A, Exclude<R, HttpService>>, never, HttpService>
contextFromServices
(
class HttpService
HttpService
)
91
)

We can see now that the type of GetTodosResolver is no longer a RequestResolver but instead it is:

const GetTodosResolver: Effect<
RequestResolver<GetTodos, never>,
never,
HttpService
>

which is an effect that access the HttpService and returns a composed resolver that has the minimal context ready to use.

Once we have such effect we can directly use it in our query definition:

const getTodos: Effect.Effect<Todo[], GetTodosError, HttpService> =
Effect.request(GetTodos({}), GetTodosResolver)

We can see that the Effect correctly requires HttpService to be provided.

Alternatively you can create RequestResolvers as part of layers direcly accessing or closing over context from construction.

Example

1
import {
import Effect
Effect
,
import Context
Context
,
import RequestResolver
RequestResolver
,
import Request
Request
,
import Layer
Layer
} from "effect"
2
3
// ------------------------------
4
// Model
5
// ------------------------------
6
25 collapsed lines
7
interface
interface User
User
{
8
readonly
(property) User._tag: "User"
_tag
: "User"
9
readonly
(property) User.id: number
id
: number
10
readonly
(property) User.name: string
name
: string
11
readonly
(property) User.email: string
email
: string
12
}
13
14
class
class GetUserError
GetUserError
{
15
readonly
(property) GetUserError._tag: "GetUserError"
_tag
= "GetUserError"
16
}
17
18
interface
interface Todo
Todo
{
19
readonly
(property) Todo._tag: "Todo"
_tag
: "Todo"
20
readonly
(property) Todo.id: number
id
: number
21
readonly
(property) Todo.message: string
message
: string
22
readonly
(property) Todo.ownerId: number
ownerId
: number
23
}
24
25
class
class GetTodosError
GetTodosError
{
26
readonly
(property) GetTodosError._tag: "GetTodosError"
_tag
= "GetTodosError"
27
}
28
29
class
class SendEmailError
SendEmailError
{
30
readonly
(property) SendEmailError._tag: "SendEmailError"
_tag
= "SendEmailError"
31
}
32
33
// ------------------------------
34
// Requests
35
// ------------------------------
36
29 collapsed lines
37
// Define a request to get multiple Todo items which might
38
// fail with a GetTodosError
39
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
40
readonly
(property) GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
41
}
42
43
// Create a tagged constructor for GetTodos requests
44
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new `Request`.

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
45
46
// Define a request to fetch a User by ID which might
47
// fail with a GetUserError
48
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
49
readonly
(property) GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
50
readonly
(property) GetUserById.id: number
id
: number
51
}
52
53
// Create a tagged constructor for GetUserById requests
54
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new `Request`.

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
55
56
// Define a request to send an email which might
57
// fail with a SendEmailError
58
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never> namespace Request

A `Request<A, E>` is a request from a data source for a value of type `A` that may fail with an `E`.

Request
<void,
class SendEmailError
SendEmailError
> {
59
readonly
(property) SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
60
readonly
(property) SendEmail.address: string
address
: string
61
readonly
(property) SendEmail.text: string
text
: string
62
}
63
64
// Create a tagged constructor for SendEmail requests
65
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new `Request`.

tagged
<
interface SendEmail
SendEmail
>("SendEmail")
66
67
// ------------------------------
68
// Resolvers With Context
69
// ------------------------------
70
21 collapsed lines
71
class
class HttpService
HttpService
extends
import Context
Context
.
const Tag: <"HttpService">(id: "HttpService") => <Self, Shape>() => Context.TagClass<Self, "HttpService", Shape> namespace Tag
Tag
("HttpService")<
72
class HttpService
HttpService
,
73
{
(property) fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
: typeof
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
}
74
>() {}
75
76
const
const GetTodosResolver: Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>
GetTodosResolver
=
77
// we create a normal resolver like we did before
78
import RequestResolver
RequestResolver
.
const fromEffect: <HttpService, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, HttpService>) => RequestResolver.RequestResolver<...>

Constructs a data source from an effectual function.

fromEffect
((
(parameter) _: GetTodos
_
:
interface GetTodos
GetTodos
) =>
79
import Effect
Effect
.
const andThen: <{ fetch: typeof fetch; }, never, HttpService, Effect.Effect<Todo[], GetTodosError, never>>(self: Effect.Effect<{ fetch: typeof fetch; }, never, HttpService>, f: (a: { fetch: typeof fetch; }) => Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
(
class HttpService
HttpService
, (
(parameter) http: { fetch: typeof fetch; }
http
) =>
80
import Effect
Effect
.
const tryPromise: <Todo[], GetTodosError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>; readonly catch: (error: unknown) => GetTodosError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
81
(property) try: (signal: AbortSignal) => PromiseLike<Todo[]>
try
: () =>
82
(parameter) http: { fetch: typeof fetch; }
http
83
.
(property) fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
("https://api.example.demo/todos")
84
.
(method) Promise<Response>.then<Todo[], never>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>),
85
(property) catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError(): GetTodosError
GetTodosError
()
86
})
87
)
88
).
(method) Pipeable.pipe<RequestResolver.RequestResolver<GetTodos, HttpService>, Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>>(this: RequestResolver.RequestResolver<...>, ab: (_: RequestResolver.RequestResolver<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
89
// we list the tags that the resolver can access
90
import RequestResolver
RequestResolver
.
const contextFromServices: <[typeof HttpService]>(services_0: typeof HttpService) => <R, A>(self: RequestResolver.RequestResolver<A, R>) => Effect.Effect<RequestResolver.RequestResolver<A, Exclude<R, HttpService>>, never, HttpService>
contextFromServices
(
class HttpService
HttpService
)
91
)
92
93
// ------------------------------
94
// Layers
95
// ------------------------------
96
97
class
class TodosService
TodosService
extends
import Context
Context
.
const Tag: <"TodosService">(id: "TodosService") => <Self, Shape>() => Context.TagClass<Self, "TodosService", Shape> namespace Tag
Tag
("TodosService")<
98
class TodosService
TodosService
,
99
{
100
(property) getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
:
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
>
101
}
102
>() {}
103
104
const
const TodosServiceLive: Layer.Layer<TodosService, never, HttpService>
TodosServiceLive
=
import Layer
Layer
.
const effect: <typeof TodosService, never, HttpService>(tag: typeof TodosService, effect: Effect.Effect<{ getTodos: Effect.Effect<Array<Todo>, GetTodosError>; }, never, HttpService>) => Layer.Layer<...> (+1 overload)

Constructs a layer from the specified effect.

effect
(
105
class TodosService
TodosService
,
106
import Effect
Effect
.
const gen: <YieldWrap<Context.Tag<HttpService, { fetch: typeof fetch; }>>, { getTodos: Effect.Effect<Todo[], GetTodosError, never>; }>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)
gen
(function* () {
107
const
const http: { fetch: typeof fetch; }
http
= yield*
class HttpService
HttpService
108
const
const resolver: RequestResolver.RequestResolver<GetTodos, never>
resolver
=
import RequestResolver
RequestResolver
.
const fromEffect: <never, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, never>) => RequestResolver.RequestResolver<GetTodos, never>

Constructs a data source from an effectual function.

fromEffect
((
(parameter) _: GetTodos
_
:
interface GetTodos
GetTodos
) =>
109
import Effect
Effect
.
const tryPromise: <any, GetTodosError>(options: { readonly try: (signal: AbortSignal) => PromiseLike<any>; readonly catch: (error: unknown) => GetTodosError; }) => Effect.Effect<...> (+1 overload)

Create an `Effect` that when executed will construct `promise` and wait for its result, errors will produce failure as `unknown`. An optional `AbortSignal` can be provided to allow for interruption of the wrapped Promise api.

tryPromise
({
110
(property) try: (signal: AbortSignal) => PromiseLike<any>
try
: () =>
111
const http: { fetch: typeof fetch; }
http
112
.
(property) fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
("https://api.example.demo/todos")
113
.
(method) Promise<Response>.then<any, Todo[]>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => Todo[] | PromiseLike<Todo[]>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
<any,
interface Todo
Todo
[]>((
(parameter) res: Response
res
) =>
(parameter) res: Response
res
.
(property) BodyMixin.json: () => Promise<unknown>
json
()),
114
(property) catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError(): GetTodosError
GetTodosError
()
115
})
116
)
117
return {
118
(property) getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
:
import Effect
Effect
.
const request: <RequestResolver.RequestResolver<GetTodos, never>, GetTodos>(self: GetTodos, dataSource: RequestResolver.RequestResolver<GetTodos, never>) => Effect.Effect<...> (+1 overload)
request
(
const GetTodos: Request.Request<out A, out E = never>.Constructor (args: Omit<GetTodos, typeof Request.RequestTypeId | "_tag">) => GetTodos
GetTodos
({}),
const resolver: RequestResolver.RequestResolver<GetTodos, never>
resolver
)
119
}
120
})
121
)
122
123
const
const getTodos: Effect.Effect<Todo[], GetTodosError, TodosService>
getTodos
:
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
124
interface Array<T>
Array
<
interface Todo
Todo
>,
125
class GetTodosError
GetTodosError
,
126
class TodosService
TodosService
127
> =
import Effect
Effect
.
const andThen: <{ getTodos: Effect.Effect<Array<Todo>, GetTodosError>; }, never, TodosService, Effect.Effect<Todo[], GetTodosError, never>>(self: Effect.Effect<{ getTodos: Effect.Effect<Array<Todo>, GetTodosError>; }, never, TodosService>, f: (a: { getTodos: Effect.Effect<Array<Todo>, GetTodosError>; }) => Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
(
class TodosService
TodosService
, (
(parameter) service: { getTodos: Effect.Effect<Array<Todo>, GetTodosError>; }
service
) =>
(parameter) service: { getTodos: Effect.Effect<Array<Todo>, GetTodosError>; }
service
.
(property) getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
)

This way is probably the best for most of the cases given that layers are the natural primitive where to wire services together.

While we have significantly optimized request batching, there’s another area that can enhance our application’s efficiency: caching. Without caching, even with optimized batch processing, the same requests could be executed multiple times, leading to unnecessary data fetching.

In the Effect library, caching is handled through built-in utilities that allow requests to be stored temporarily, preventing the need to re-fetch data that hasn’t changed. This feature is crucial for reducing the load on both the server and the network, especially in applications that make frequent similar requests.

Here’s how you can implement caching for the getUserById query:

const getUserById = (id: number) =>
Effect.request(GetUserById({ id }), GetUserByIdResolver).pipe(
Effect.withRequestCaching(true)
)

Assuming you’ve wired everything up correctly:

const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
concurrency: "unbounded"
})
}).pipe(Effect.repeat(Schedule.fixed("10 seconds")))

With this program, the getTodos operation retrieves the todos for each user. Then, the Effect.forEach function is used to notify the owner of each todo concurrently, without waiting for the notifications to complete.

The repeat function is applied to the entire chain of operations, and it ensures that the program repeats every 10 seconds using a fixed schedule. This means that the entire process, including fetching todos and sending notifications, will be executed repeatedly with a 10-second interval.

The program incorporates a caching mechanism, which prevents the same GetUserById operation from being executed more than once within a span of 1 minute. This default caching behavior helps optimize the program’s execution and reduces unnecessary requests to fetch user data.

Furthermore, the program is designed to send emails in batches, allowing for efficient processing and better utilization of resources.

In real-world applications, effective caching strategies can significantly improve performance by reducing redundant data fetching. The Effect library provides flexible caching mechanisms that can be tailored for specific parts of your application or applied globally.

There may be scenarios where different parts of your application have unique caching requirements—some might benefit from a localized cache, while others might need a global cache setup. Let’s explore how you can configure a custom cache to meet these varied needs.

Here’s how you can create a custom cache and apply it to part of your application. This example demonstrates setting up a cache that repeats a task every 10 seconds, caching requests with specific parameters like capacity and TTL (time-to-live).

const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
concurrency: "unbounded"
})
}).pipe(
Effect.repeat(Schedule.fixed("10 seconds")),
Effect.provide(
Layer.setRequestCache(
Request.makeCache({ capacity: 256, timeToLive: "60 minutes" })
)
)
)

You can also construct a cache using Request.makeCache and apply it directly to a specific program using Effect.withRequestCache. This method ensures that all requests originating from the specified program are managed through the custom cache, provided that caching is enabled.