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// Model3// ------------------------------4
5interface interface User
User {6 readonly (property) User._tag: "User"
_tag: "User"7 readonly (property) User.id: number
id: number8 readonly (property) User.name: string
name: string9 readonly (property) User.email: string
email: string10}11
12class class GetUserError
GetUserError {13 readonly (property) GetUserError._tag: "GetUserError"
_tag = "GetUserError"14}15
16interface interface Todo
Todo {17 readonly (property) Todo._tag: "Todo"
_tag: "Todo"18 readonly (property) Todo.id: number
id: number19 readonly (property) Todo.message: string
message: string20 readonly (property) Todo.ownerId: number
ownerId: number21}22
23class class GetTodosError
GetTodosError {24 readonly (property) GetTodosError._tag: "GetTodosError"
_tag = "GetTodosError"25}26
27class 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.
1import { import Effect
Effect } from "effect"2
3// ------------------------------4// Model5// ------------------------------6
25 collapsed lines
7interface interface User
User {8 readonly (property) User._tag: "User"
_tag: "User"9 readonly (property) User.id: number
id: number10 readonly (property) User.name: string
name: string11 readonly (property) User.email: string
email: string12}13
14class class GetUserError
GetUserError {15 readonly (property) GetUserError._tag: "GetUserError"
_tag = "GetUserError"16}17
18interface interface Todo
Todo {19 readonly (property) Todo._tag: "Todo"
_tag: "Todo"20 readonly (property) Todo.id: number
id: number21 readonly (property) Todo.message: string
message: string22 readonly (property) Todo.ownerId: number
ownerId: number23}24
25class class GetTodosError
GetTodosError {26 readonly (property) GetTodosError._tag: "GetTodosError"
_tag = "GetTodosError"27}28
29class class SendEmailError
SendEmailError {30 readonly (property) SendEmailError._tag: "SendEmailError"
_tag = "SendEmailError"31}32
33// ------------------------------34// API35// ------------------------------36
37// Fetches a list of todos from an external API38const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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 API47const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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 API57const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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 first71const 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 email77const 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.
1import { import Effect
Effect } from "effect"2
3// ------------------------------4// Model5// ------------------------------6
25 collapsed lines
7interface interface User
User {8 readonly (property) User._tag: "User"
_tag: "User"9 readonly (property) User.id: number
id: number10 readonly (property) User.name: string
name: string11 readonly (property) User.email: string
email: string12}13
14class class GetUserError
GetUserError {15 readonly (property) GetUserError._tag: "GetUserError"
_tag = "GetUserError"16}17
18interface interface Todo
Todo {19 readonly (property) Todo._tag: "Todo"
_tag: "Todo"20 readonly (property) Todo.id: number
id: number21 readonly (property) Todo.message: string
message: string22 readonly (property) Todo.ownerId: number
ownerId: number23}24
25class class GetTodosError
GetTodosError {26 readonly (property) GetTodosError._tag: "GetTodosError"
_tag = "GetTodosError"27}28
29class class SendEmailError
SendEmailError {30 readonly (property) SendEmailError._tag: "SendEmailError"
_tag = "SendEmailError"31}32
33// ------------------------------34// API35// ------------------------------36
46 collapsed lines
37// Fetches a list of todos from an external API38const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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 API47const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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 API57const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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 first71const 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 email77const 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 owners85const 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>
getTodos87 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
-
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.
-
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.
-
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.
1import { import Request
Request } from "effect"2
3// ------------------------------4// Model5// ------------------------------6
25 collapsed lines
7interface interface User
User {8 readonly (property) User._tag: "User"
_tag: "User"9 readonly (property) User.id: number
id: number10 readonly (property) User.name: string
name: string11 readonly (property) User.email: string
email: string12}13
14class class GetUserError
GetUserError {15 readonly (property) GetUserError._tag: "GetUserError"
_tag = "GetUserError"16}17
18interface interface Todo
Todo {19 readonly (property) Todo._tag: "Todo"
_tag: "Todo"20 readonly (property) Todo.id: number
id: number21 readonly (property) Todo.message: string
message: string22 readonly (property) Todo.ownerId: number
ownerId: number23}24
25class class GetTodosError
GetTodosError {26 readonly (property) GetTodosError._tag: "GetTodosError"
_tag = "GetTodosError"27}28
29class class SendEmailError
SendEmailError {30 readonly (property) SendEmailError._tag: "SendEmailError"
_tag = "SendEmailError"31}32
33// ------------------------------34// Requests35// ------------------------------36
37// Define a request to get multiple Todo items which might38// fail with a GetTodosError39interface 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 requests44const 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 might47// fail with a GetUserError48interface 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: number51}52
53// Create a tagged constructor for GetUserById requests54const 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 might57// fail with a SendEmailError58interface 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: string61 readonly (property) SendEmail.text: string
text: string62}63
64// Create a tagged constructor for SendEmail requests65const 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.
1import { import Effect
Effect, import Request
Request, import RequestResolver
RequestResolver } from "effect"2
3// ------------------------------4// Model5// ------------------------------6
25 collapsed lines
7interface interface User
User {8 readonly (property) User._tag: "User"
_tag: "User"9 readonly (property) User.id: number
id: number10 readonly (property) User.name: string
name: string11 readonly (property) User.email: string
email: string12}13
14class class GetUserError
GetUserError {15 readonly (property) GetUserError._tag: "GetUserError"
_tag = "GetUserError"16}17
18interface interface Todo
Todo {19 readonly (property) Todo._tag: "Todo"
_tag: "Todo"20 readonly (property) Todo.id: number
id: number21 readonly (property) Todo.message: string
message: string22 readonly (property) Todo.ownerId: number
ownerId: number23}24
25class class GetTodosError
GetTodosError {26 readonly (property) GetTodosError._tag: "GetTodosError"
_tag = "GetTodosError"27}28
29class class SendEmailError
SendEmailError {30 readonly (property) SendEmailError._tag: "SendEmailError"
_tag = "SendEmailError"31}32
33// ------------------------------34// Requests35// ------------------------------36
29 collapsed lines
37// Define a request to get multiple Todo items which might38// fail with a GetTodosError39interface 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 requests44const 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 might47// fail with a GetUserError48interface 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: number51}52
53// Create a tagged constructor for GetUserById requests54const 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 might57// fail with a SendEmailError58interface 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: string61 readonly (property) SendEmail.text: string
text: string62}63
64// Create a tagged constructor for SendEmail requests65const 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// Resolvers69// ------------------------------70
71// Assuming GetTodos cannot be batched, we create a standard resolver72const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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 resolver84const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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>
Creates an `Effect` that succeeds with the provided value.
Use this function to represent a successful computation that yields a value of type `A`.
The effect does not fail and does not require any environmental context.
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>
Creates an `Effect` that represents a recoverable error.
This `Effect` does not succeed but instead fails with the provided error. The
failure can be of any type, and will propagate through the effect pipeline
unless handled.
Use this function when you want to explicitly signal an error in an `Effect`
computation. The failed effect can later be handled with functions like
{@link
catchAll
}
or
{@link
catchTag
}
.
fail((parameter) error: GetUserError
error))107 )108 )109 )110)111
112// Assuming SendEmail can be batched, we create a batched resolver113const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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
text126 }))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>
Creates an `Effect` that represents a recoverable error.
This `Effect` does not succeed but instead fails with the provided error. The
failure can be of any type, and will propagate through the effect pipeline
unless handled.
Use this function when you want to explicitly signal an error in an `Effect`
computation. The failed effect can later be handled with functions like
{@link
catchAll
}
or
{@link
catchTag
}
.
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.
1import { import Effect
Effect, import Request
Request, import RequestResolver
RequestResolver } from "effect"2
3// ------------------------------4// Model5// ------------------------------6
25 collapsed lines
7interface interface User
User {8 readonly (property) User._tag: "User"
_tag: "User"9 readonly (property) User.id: number
id: number10 readonly (property) User.name: string
name: string11 readonly (property) User.email: string
email: string12}13
14class class GetUserError
GetUserError {15 readonly (property) GetUserError._tag: "GetUserError"
_tag = "GetUserError"16}17
18interface interface Todo
Todo {19 readonly (property) Todo._tag: "Todo"
_tag: "Todo"20 readonly (property) Todo.id: number
id: number21 readonly (property) Todo.message: string
message: string22 readonly (property) Todo.ownerId: number
ownerId: number23}24
25class class GetTodosError
GetTodosError {26 readonly (property) GetTodosError._tag: "GetTodosError"
_tag = "GetTodosError"27}28
29class class SendEmailError
SendEmailError {30 readonly (property) SendEmailError._tag: "SendEmailError"
_tag = "SendEmailError"31}32
33// ------------------------------34// Requests35// ------------------------------36
29 collapsed lines
37// Define a request to get multiple Todo items which might38// fail with a GetTodosError39interface 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 requests44const 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 might47// fail with a GetUserError48interface 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: number51}52
53// Create a tagged constructor for GetUserById requests54const 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 might57// fail with a SendEmailError58interface 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: string61 readonly (property) SendEmail.text: string
text: string62}63
64// Create a tagged constructor for SendEmail requests65const 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// Resolvers69// ------------------------------70
72 collapsed lines
71// Assuming GetTodos cannot be batched, we create a standard resolver72const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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 resolver84const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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>
Creates an `Effect` that succeeds with the provided value.
Use this function to represent a successful computation that yields a value of type `A`.
The effect does not fail and does not require any environmental context.
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>
Creates an `Effect` that represents a recoverable error.
This `Effect` does not succeed but instead fails with the provided error. The
failure can be of any type, and will propagate through the effect pipeline
unless handled.
Use this function when you want to explicitly signal an error in an `Effect`
computation. The failed effect can later be handled with functions like
{@link
catchAll
}
or
{@link
catchTag
}
.
fail((parameter) error: GetUserError
error))107 )108 )109 )110)111
112// Assuming SendEmail can be batched, we create a batched resolver113const 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
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
text126 }))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>
Creates an `Effect` that represents a recoverable error.
This `Effect` does not succeed but instead fails with the provided error. The
failure can be of any type, and will propagate through the effect pipeline
unless handled.
Use this function when you want to explicitly signal an error in an `Effect`
computation. The failed effect can later be handled with functions like
{@link
catchAll
}
or
{@link
catchTag
}
.
fail((parameter) error: SendEmailError
error))139 )140 )141 )142)143
144// ------------------------------145// Queries146// ------------------------------147
148// Defines a query to fetch all Todo items149const 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
GetTodosError152> = 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 ID155const 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 address159const 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 user163const 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 notification169const 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:
1import { import Effect
Effect, import Context
Context, import RequestResolver
RequestResolver, import Request
Request } from "effect"2
3// ------------------------------4// Model5// ------------------------------6
25 collapsed lines
7interface interface User
User {8 readonly (property) User._tag: "User"
_tag: "User"9 readonly (property) User.id: number
id: number10 readonly (property) User.name: string
name: string11 readonly (property) User.email: string
email: string12}13
14class class GetUserError
GetUserError {15 readonly (property) GetUserError._tag: "GetUserError"
_tag = "GetUserError"16}17
18interface interface Todo
Todo {19 readonly (property) Todo._tag: "Todo"
_tag: "Todo"20 readonly (property) Todo.id: number
id: number21 readonly (property) Todo.message: string
message: string22 readonly (property) Todo.ownerId: number
ownerId: number23}24
25class class GetTodosError
GetTodosError {26 readonly (property) GetTodosError._tag: "GetTodosError"
_tag = "GetTodosError"27}28
29class class SendEmailError
SendEmailError {30 readonly (property) SendEmailError._tag: "SendEmailError"
_tag = "SendEmailError"31}32
33// ------------------------------34// Requests35// ------------------------------36
29 collapsed lines
37// Define a request to get multiple Todo items which might38// fail with a GetTodosError39interface 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 requests44const 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 might47// fail with a GetUserError48interface 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: number51}52
53// Create a tagged constructor for GetUserById requests54const 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 might57// fail with a SendEmailError58interface 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: string61 readonly (property) SendEmail.text: string
text: string62}63
64// Create a tagged constructor for SendEmail requests65const 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 Context69// ------------------------------70
71class 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
76const const GetTodosResolver: Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>
GetTodosResolver =77 // we create a normal resolver like we did before78 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
tryPromise({81 (property) try: (signal: AbortSignal) => PromiseLike<Todo[]>
try: () =>82 (parameter) http: {
fetch: typeof fetch;
}
http83 .(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 access90 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 RequestResolver
s as part of layers direcly accessing or closing over context from construction.
Example
1import { import Effect
Effect, import Context
Context, import RequestResolver
RequestResolver, import Request
Request, import Layer
Layer } from "effect"2
3// ------------------------------4// Model5// ------------------------------6
25 collapsed lines
7interface interface User
User {8 readonly (property) User._tag: "User"
_tag: "User"9 readonly (property) User.id: number
id: number10 readonly (property) User.name: string
name: string11 readonly (property) User.email: string
email: string12}13
14class class GetUserError
GetUserError {15 readonly (property) GetUserError._tag: "GetUserError"
_tag = "GetUserError"16}17
18interface interface Todo
Todo {19 readonly (property) Todo._tag: "Todo"
_tag: "Todo"20 readonly (property) Todo.id: number
id: number21 readonly (property) Todo.message: string
message: string22 readonly (property) Todo.ownerId: number
ownerId: number23}24
25class class GetTodosError
GetTodosError {26 readonly (property) GetTodosError._tag: "GetTodosError"
_tag = "GetTodosError"27}28
29class class SendEmailError
SendEmailError {30 readonly (property) SendEmailError._tag: "SendEmailError"
_tag = "SendEmailError"31}32
33// ------------------------------34// Requests35// ------------------------------36
29 collapsed lines
37// Define a request to get multiple Todo items which might38// fail with a GetTodosError39interface 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 requests44const 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 might47// fail with a GetUserError48interface 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: number51}52
53// Create a tagged constructor for GetUserById requests54const 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 might57// fail with a SendEmailError58interface 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: string61 readonly (property) SendEmail.text: string
text: string62}63
64// Create a tagged constructor for SendEmail requests65const 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 Context69// ------------------------------70
21 collapsed lines
71class 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
76const const GetTodosResolver: Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>
GetTodosResolver =77 // we create a normal resolver like we did before78 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
tryPromise({81 (property) try: (signal: AbortSignal) => PromiseLike<Todo[]>
try: () =>82 (parameter) http: {
fetch: typeof fetch;
}
http83 .(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 access90 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// Layers95// ------------------------------96
97class 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
104const 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
HttpService108 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)
Creates an `Effect` that represents an asynchronous computation that might fail.
If the `Promise` returned by `evaluate` rejects, the error is caught and the effect fails with an `UnknownException`.
An optional `AbortSignal` can be provided to allow for interruption of the
wrapped `Promise` API.
**Overload with custom error handling:**
Creates an `Effect` that represents an asynchronous computation that might fail, with custom error mapping.
If the `Promise` rejects, the `catch` function maps the error to an error of type `E`.
tryPromise({110 (property) try: (signal: AbortSignal) => PromiseLike<any>
try: () =>111 const http: {
fetch: typeof fetch;
}
http112 .(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
123const 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
TodosService127> = 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.