/** * The `Request` module provides a way to model requests to external data sources * in a functional and composable manner. Requests represent descriptions of * operations that can be batched, cached, and executed efficiently. * * A `Request` represents a request that: * - Yields a value of type `A` on success * - Can fail with an error of type `E` * - Requires services of type `R` * * Requests are primarily used with RequestResolver to implement efficient * data fetching patterns, including automatic batching and caching. * * @since 2.0.0 */ import type * as Cause from "./Cause.ts" import type * as Context from "./Context.ts" import type * as Effect from "./Effect.ts" import * as Equal from "./Equal.ts" import type * as Exit from "./Exit.ts" import { dual } from "./Function.ts" import * as core from "./internal/core.ts" import * as internalEffect from "./internal/effect.ts" import { hasProperty } from "./Predicate.ts" import type * as Types from "./Types.ts" const TypeId = "~effect/Request" /** * A `Request` is a request from a data source for a value of type `A` * that may fail with an `E` and have requirements of type `R`. * * @example * ```ts * import type { Request } from "effect" * * // Define a request that fetches a user by ID * interface GetUser extends Request.Request { * readonly _tag: "GetUser" * readonly id: number * } * * // Define a request that fetches all users * interface GetAllUsers extends Request.Request, Error> { * readonly _tag: "GetAllUsers" * } * ``` * * @since 2.0.0 * @category models */ export interface Request extends Variance {} /** * @since 2.0.0 * @category models */ export type Any = Request /** * @since 2.0.0 * @category models */ export interface Variance { readonly [TypeId]: { readonly _A: Types.Covariant readonly _E: Types.Covariant readonly _R: Types.Covariant } } /** * @example * ```ts * import { Request } from "effect" * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" * readonly id: number * } * * // Constructor type is used internally by Request.of() and Request.tagged() * const GetUser = Request.tagged("GetUser") * const userRequest = GetUser({ id: 123 }) * ``` * * @since 2.0.0 * @category models */ export interface Constructor, T extends keyof R = never> { (args: Types.VoidIfEmpty)>>>): R } /** * A utility type to extract the error type from a `Request`. * * @example * ```ts * import type { Request } from "effect" * * interface GetUser extends Request.Request { * readonly id: number * } * * // Extract the error type from a Request using the utility * type UserError = Request.Error // Error * ``` * * @since 2.0.0 * @category type-level */ export type Error> = [T] extends [Request] ? _E : never /** * A utility type to extract the value type from a `Request`. * * @example * ```ts * import type { Request } from "effect" * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" * readonly id: number * } * * // Extract the success type from a Request using the utility * type UserSuccess = Request.Success // string * ``` * * @since 2.0.0 * @category type-level */ export type Success> = [T] extends [Request] ? _A : never /** * A utility type to extract the requirements type from a `Request`. * * @since 4.0.0 * @category type-level */ export type Services> = [T] extends [Request] ? _R : never /** * A utility type to extract the result type from a `Request`. * * @example * ```ts * import type { Request } from "effect" * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" * readonly id: number * } * * // Extract the result type from a Request using the utility * type UserResult = Request.Result // Exit.Exit * ``` * * @since 2.0.0 * @category type-level */ export type Result> = T extends Request ? Exit.Exit : never const requestVariance = Equal.byReferenceUnsafe({ /* c8 ignore next */ _E: (_: never) => _, /* c8 ignore next */ _A: (_: never) => _, /* c8 ignore next */ _R: (_: never) => _ }) /** * @since 4.0.0 */ export const RequestPrototype: Request = { ...core.StructuralProto, [TypeId]: requestVariance } /** * Tests if a value is a `Request`. * * @example * ```ts * import { Request } from "effect" * * declare const User: unique symbol * declare const UserNotFound: unique symbol * type User = typeof User * type UserNotFound = typeof UserNotFound * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" * readonly id: string * } * const GetUser = Request.tagged("GetUser") * * const request = GetUser({ id: "123" }) * console.log(Request.isRequest(request)) // true * console.log(Request.isRequest("not a request")) // false * ``` * * @category guards * @since 2.0.0 */ export const isRequest = (u: unknown): u is Request => hasProperty(u, TypeId) /** * Creates a constructor function for a specific Request type. * * @example * ```ts * import { Request } from "effect" * * declare const UserProfile: unique symbol * declare const ProfileError: unique symbol * type UserProfile = typeof UserProfile * type ProfileError = typeof ProfileError * * interface GetUserProfile extends Request.Request { * readonly id: string * readonly includeSettings: boolean * } * * const GetUserProfile = Request.of() * * const request = GetUserProfile({ * id: "user-123", * includeSettings: true * }) * ``` * * @category constructors * @since 2.0.0 */ export const of = >(): Constructor => (args) => Object.assign(Object.create(RequestPrototype), args) /** * Creates a constructor function for a tagged Request type. The tag is automatically * added to the request, making it useful for discriminated unions. * * @example * ```ts * import { Request } from "effect" * * declare const User: unique symbol * declare const UserNotFound: unique symbol * declare const Post: unique symbol * declare const PostNotFound: unique symbol * type User = typeof User * type UserNotFound = typeof UserNotFound * type Post = typeof Post * type PostNotFound = typeof PostNotFound * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" * readonly id: string * } * * interface GetPost extends Request.Request { * readonly _tag: "GetPost" * readonly id: string * } * * const GetUser = Request.tagged("GetUser") * const GetPost = Request.tagged("GetPost") * * const userRequest = GetUser({ id: "user-123" }) * const postRequest = GetPost({ id: "post-456" }) * * // _tag is automatically set * console.log(userRequest._tag) // "GetUser" * console.log(postRequest._tag) // "GetPost" * ``` * * @category constructors * @since 2.0.0 */ export const tagged = & { _tag: string }>( tag: R["_tag"] ): Constructor => (args) => { const request = Object.create(RequestPrototype) if (args) Object.assign(request, args) request._tag = tag return request } /** * @example * ```ts * import { Request } from "effect" * * class GetUser extends Request.Class<{ id: number }, string, Error> { * constructor(readonly id: number) { * super({ id }) * } * } * * const getUserRequest = new GetUser(123) * console.log(getUserRequest.id) // 123 * ``` * * @since 2.0.0 * @category constructors */ export const Class: new, Success, Error = never, Context = never>( args: Types.Equals>, {}> extends true ? void : { readonly [P in keyof A as P extends keyof Request ? never : P]: A[P] } ) => Request & Readonly = (function() { function Class(this: any, args: any) { if (args) { Object.assign(this, args) } } Class.prototype = RequestPrototype return Class as any })() /** * @example * ```ts * import { Request } from "effect" * * class GetUserById * extends Request.TaggedClass("GetUserById")<{ id: number }, string, Error> * {} * * const request = new GetUserById({ id: 123 }) * console.log(request._tag) // "GetUserById" * console.log(request.id) // 123 * ``` * * @since 2.0.0 * @category constructors */ export const TaggedClass = ( tag: Tag ): new, Success, Error = never, Services = never>( args: Types.Equals>, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" | keyof Request ? never : P]: A[P] } ) => Request & Readonly & { readonly _tag: Tag } => { return class TaggedClass extends Class { readonly _tag = tag } as any } /** * Completes a request entry with the provided result. This is typically used * within RequestResolver implementations to fulfill pending requests. * * @category completion * @since 2.0.0 */ export const complete: { /** * Completes a request entry with the provided result. This is typically used * within RequestResolver implementations to fulfill pending requests. * * @category completion * @since 2.0.0 */ (result: Result): (self: Entry) => Effect.Effect /** * Completes a request entry with the provided result. This is typically used * within RequestResolver implementations to fulfill pending requests. * * @category completion * @since 2.0.0 */ (self: Entry, result: Result): Effect.Effect } = dual( 2, (self: Entry, result: Result): Effect.Effect => internalEffect.sync(() => self.completeUnsafe(result)) ) /** * @since 2.0.0 * @category completion */ export const completeEffect: { /** * @since 2.0.0 * @category completion */ (effect: Effect.Effect, Error, R>): (self: Entry) => Effect.Effect /** * @since 2.0.0 * @category completion */ (self: Entry, effect: Effect.Effect, Error, R>): Effect.Effect } = dual( 2, (self: Entry, effect: Effect.Effect, Error, R>): Effect.Effect => internalEffect.matchEffect(effect, { onFailure: (error) => complete(self, core.exitFail(error) as any), onSuccess: (value) => complete(self, core.exitSucceed(value) as any) }) ) /** * @since 2.0.0 * @category completion */ export const fail: { /** * @since 2.0.0 * @category completion */ (error: Error): (self: Entry) => Effect.Effect /** * @since 2.0.0 * @category completion */ (self: Entry, error: Error): Effect.Effect } = dual( 2, (self: Entry, error: Error): Effect.Effect => complete(self, core.exitFail(error) as any) ) /** * @since 2.0.0 * @category completion */ export const failCause: { /** * @since 2.0.0 * @category completion */ (cause: Cause.Cause>): (self: Entry) => Effect.Effect /** * @since 2.0.0 * @category completion */ (self: Entry, cause: Cause.Cause>): Effect.Effect } = dual( 2, (self: Entry, cause: Cause.Cause>): Effect.Effect => complete(self, core.exitFailCause(cause) as any) ) /** * @since 2.0.0 * @category completion */ export const succeed: { /** * @since 2.0.0 * @category completion */ (value: Success): (self: Entry) => Effect.Effect /** * @since 2.0.0 * @category completion */ (self: Entry, value: Success): Effect.Effect } = dual( 2, (self: Entry, value: Success): Effect.Effect => complete(self, core.exitSucceed(value) as any) ) /** * @since 2.0.0 * @category entry */ export interface Entry { readonly request: R readonly context: Context.Context< [R] extends [Request] ? _R : never > uninterruptible: boolean completeUnsafe( exit: Exit.Exit< [R] extends [Request] ? _A : never, [R] extends [Request] ? _E : never > ): void } /** * @since 2.0.0 * @category entry */ export const makeEntry = (options: { readonly request: R readonly context: Context.Context< [R] extends [Request] ? _R : never > readonly uninterruptible: boolean readonly completeUnsafe: ( exit: Exit.Exit< [R] extends [Request] ? _A : never, [R] extends [Request] ? _E : never > ) => void }): Entry => options