/** * Typed request values for data loading with `Effect.request`. * * A request describes one logical piece of work without performing it. It * records the success type, typed error, service requirements, and fields a * resolver needs to complete the request. Requests are paired with * `RequestResolver`, which performs backend-specific loading and completes each * pending request entry with a success, failure, cause, exit, or effect. * * @stability stable * @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 * as InternalRecord from "./internal/record.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** (Defining typed requests) * * ```ts import.meta.vitest * 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" * } * * ``` * * @stability stable * @category models * @since 2.0.0 */ export interface Request extends Variance {} /** * Alias for any `Request`, regardless of its success, error, or service * requirements. * * **When to use** * * Use as a generic constraint for APIs that accept any request while preserving * each concrete request's success, error, and service types. * * @see {@link Request} for the request interface * @see {@link Success} for extracting a request's success type * @see {@link Error} for extracting a request's error type * @see {@link Services} for extracting a request's service requirements * @see {@link Result} for the exit type produced by completing a request * * @stability stable * @category utility types * @since 4.0.0 */ export type Any = Request /** * Variance marker carried by every `Request`. * * **Details** * * This marker preserves the success, error, and service requirement types for * Effect's type-level machinery. Users normally get it by extending `Request`. * * @stability stable * @category models * @since 2.0.0 */ export interface Variance { readonly [TypeId]: { readonly _A: Types.Covariant readonly _E: Types.Covariant readonly _R: Types.Covariant } } /** * The constructor type returned by `Request.of` and `Request.tagged`. * * **Details** * * The constructor accepts the request's data fields, excluding request variance * fields and any fields already supplied by the constructor such as `_tag`, and * returns a value of the request type. * * **Example** (Using generated request constructors) * * ```ts import.meta.vitest * 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 request = GetUser({ id: 123 }) * * request._tag // => "GetUser" * request.id // => 123 * ``` * * @stability stable * @category models * @since 2.0.0 */ export interface Constructor, T extends keyof R = never> { (args: Types.VoidIfEmpty)>>>): R } /** * A utility type to extract the error type from a `Request`. * * **Example** (Extracting a request error type) * * ```ts import.meta.vitest * 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 * * ``` * * @stability stable * @category utility types * @since 2.0.0 */ export type Error> = [T] extends [Request] ? _E : never /** * A utility type to extract the value type from a `Request`. * * **Example** (Extracting a request success type) * * ```ts import.meta.vitest * 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 * * ``` * * @stability stable * @category utility types * @since 2.0.0 */ export type Success> = [T] extends [Request] ? _A : never /** * A utility type to extract the requirements type from a `Request`. * * @stability stable * @category utility types * @since 4.0.0 */ export type Services> = [T] extends [Request] ? _R : never /** * A utility type to extract the result type from a `Request`. * * **Example** (Extracting a request result type) * * ```ts import.meta.vitest * 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 * * ``` * * @stability stable * @category utility types * @since 2.0.0 */ 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) => _ }) /** * Prototype used by Effect's request constructors. * * **Details** * * This low-level value provides the structural request marker for values * created by `Request.of`, `Request.tagged`, `Request.Class`, and * `Request.TaggedClass`. Most users should use those constructors instead of * interacting with the prototype directly. * * @stability stable * @category prototypes * @since 4.0.0 */ export const RequestPrototype: Request = { ...core.StructuralProto, [TypeId]: requestVariance } /** * Checks whether a value is a `Request`. * * **Example** (Checking request values) * * ```ts import.meta.vitest * 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" }) * Request.isRequest(request) // => true * Request.isRequest("not a request") // => false * ``` * * @stability stable * @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** (Creating untagged request constructors) * * ```ts import.meta.vitest * 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 * }) * * request.id // => "user-123" * request.includeSettings // => true * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const of = >(): Constructor => (args) => Object.setPrototypeOf({ ...(args as R) }, RequestPrototype) /** * Creates a constructor function for a tagged Request type. The tag is automatically * added to the request, making it useful for discriminated unions. * * **Example** (Creating tagged request constructors) * * ```ts import.meta.vitest * 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 * Array.of(userRequest._tag, postRequest._tag) // => ["GetUser", "GetPost"] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const tagged = & { _tag: string }>( tag: R["_tag"] ): Constructor => (args) => { return Object.setPrototypeOf({ ...(args as R), _tag: tag }, RequestPrototype) } /** * Defines request types with TypeScript classes. * * **Details** * * Subclasses pass their data fields to `super`, and instances are marked as * `Request` values while retaining the provided readonly fields. * * **Example** (Defining request classes) * * ```ts import.meta.vitest * import { Request } from "effect" * * class GetUser extends Request.Class<{ id: number }, string, Error> { * constructor(readonly id: number) { * super({ id }) * } * } * * const getUserRequest = new GetUser(123) * getUserRequest.id // => 123 * ``` * * @stability stable * @category constructors * @since 2.0.0 */ 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: object, args: object | undefined) { if (args) { InternalRecord.assignProperties(this, args) } } Class.prototype = RequestPrototype return Class as any })() /** * Creates a class constructor for requests with a fixed `_tag` field. * * **Details** * * Use this when defining class-based request types that should participate in * tagged unions or tag-based request resolvers. * * **Example** (Defining tagged request classes) * * ```ts import.meta.vitest * import { Request } from "effect" * * class GetUserById * extends Request.TaggedClass("GetUserById")<{ id: number }, string, Error> * {} * * const request = new GetUserById({ id: 123 }) * * request._tag // => "GetUserById" * request.id // => 123 * ``` * * @stability stable * @category constructors * @since 2.0.0 */ 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. * * **When to use** * * Use when you need to finish a `Request.Entry` with a prebuilt final `Exit` * result. * * @see {@link completeEffect} for completing an entry from an effect that may succeed or fail * @see {@link succeed} for completing an entry with a successful value * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @category completion * @since 2.0.0 */ export const complete: { /** * Completes a request entry with the provided result. * * **When to use** * * Use when you need to finish a `Request.Entry` with a prebuilt final `Exit` * result. * * @see {@link completeEffect} for completing an entry from an effect that may succeed or fail * @see {@link succeed} for completing an entry with a successful value * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @category completion * @since 2.0.0 */ (result: Result): (self: Entry) => Effect.Effect /** * Completes a request entry with the provided result. * * **When to use** * * Use when you need to finish a `Request.Entry` with a prebuilt final `Exit` * result. * * @see {@link completeEffect} for completing an entry from an effect that may succeed or fail * @see {@link succeed} for completing an entry with a successful value * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @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)) ) /** * Completes a request entry with the result of an effect. * * **When to use** * * Use to finish a `Request.Entry` by running an effect whose success or typed * failure should become the request result. * * **Details** * * If the effect succeeds, the entry is completed successfully with its value. * If the effect fails, the entry is completed with that failure. * * **Gotchas** * * The returned effect itself does not fail with the request error. * * @see {@link complete} for completing an entry with a prebuilt `Exit` * @see {@link succeed} for completing an entry with a successful value * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @category completion * @since 2.0.0 */ export const completeEffect: { /** * Completes a request entry with the result of an effect. * * **When to use** * * Use to finish a `Request.Entry` by running an effect whose success or typed * failure should become the request result. * * **Details** * * If the effect succeeds, the entry is completed successfully with its value. * If the effect fails, the entry is completed with that failure. * * **Gotchas** * * The returned effect itself does not fail with the request error. * * @see {@link complete} for completing an entry with a prebuilt `Exit` * @see {@link succeed} for completing an entry with a successful value * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @category completion * @since 2.0.0 */ (effect: Effect.Effect, Error, R>): (self: Entry) => Effect.Effect /** * Completes a request entry with the result of an effect. * * **When to use** * * Use to finish a `Request.Entry` by running an effect whose success or typed * failure should become the request result. * * **Details** * * If the effect succeeds, the entry is completed successfully with its value. * If the effect fails, the entry is completed with that failure. * * **Gotchas** * * The returned effect itself does not fail with the request error. * * @see {@link complete} for completing an entry with a prebuilt `Exit` * @see {@link succeed} for completing an entry with a successful value * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @category completion * @since 2.0.0 */ (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) }) ) /** * Completes a request entry with a typed failure. * * **When to use** * * Use to report a request-specific typed error while implementing a * `RequestResolver`. * * @see {@link failCause} for completing an entry with a full `Cause` * @see {@link complete} for completing an entry with an existing `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link succeed} for completing an entry successfully * * @stability stable * @category completion * @since 2.0.0 */ export const fail: { /** * Completes a request entry with a typed failure. * * **When to use** * * Use to report a request-specific typed error while implementing a * `RequestResolver`. * * @see {@link failCause} for completing an entry with a full `Cause` * @see {@link complete} for completing an entry with an existing `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link succeed} for completing an entry successfully * * @stability stable * @category completion * @since 2.0.0 */ (error: Error): (self: Entry) => Effect.Effect /** * Completes a request entry with a typed failure. * * **When to use** * * Use to report a request-specific typed error while implementing a * `RequestResolver`. * * @see {@link failCause} for completing an entry with a full `Cause` * @see {@link complete} for completing an entry with an existing `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link succeed} for completing an entry successfully * * @stability stable * @category completion * @since 2.0.0 */ (self: Entry, error: Error): Effect.Effect } = dual( 2, (self: Entry, error: Error): Effect.Effect => complete(self, core.exitFail(error) as any) ) /** * Completes a request entry with a failure `Cause`. * * **When to use** * * Use when you need a `RequestResolver` to complete an entry with structured * cause information rather than only the request's typed error value. * * @see {@link fail} for completing an entry with a typed error value * @see {@link complete} for completing an entry with an existing `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link succeed} for completing an entry successfully * * @stability stable * @category completion * @since 2.0.0 */ export const failCause: { /** * Completes a request entry with a failure `Cause`. * * **When to use** * * Use when you need a `RequestResolver` to complete an entry with structured * cause information rather than only the request's typed error value. * * @see {@link fail} for completing an entry with a typed error value * @see {@link complete} for completing an entry with an existing `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link succeed} for completing an entry successfully * * @stability stable * @category completion * @since 2.0.0 */ (cause: Cause.Cause>): (self: Entry) => Effect.Effect /** * Completes a request entry with a failure `Cause`. * * **When to use** * * Use when you need a `RequestResolver` to complete an entry with structured * cause information rather than only the request's typed error value. * * @see {@link fail} for completing an entry with a typed error value * @see {@link complete} for completing an entry with an existing `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link succeed} for completing an entry successfully * * @stability stable * @category completion * @since 2.0.0 */ (self: Entry, cause: Cause.Cause>): Effect.Effect } = dual( 2, (self: Entry, cause: Cause.Cause>): Effect.Effect => complete(self, core.exitFailCause(cause) as any) ) /** * Completes a request entry successfully with the supplied value. * * **When to use** * * Use when you need to finish a `Request.Entry` with a successful request * value. * * @see {@link complete} for completing an entry with a prebuilt `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @category completion * @since 2.0.0 */ export const succeed: { /** * Completes a request entry successfully with the supplied value. * * **When to use** * * Use when you need to finish a `Request.Entry` with a successful request * value. * * @see {@link complete} for completing an entry with a prebuilt `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @category completion * @since 2.0.0 */ (value: Success): (self: Entry) => Effect.Effect /** * Completes a request entry successfully with the supplied value. * * **When to use** * * Use when you need to finish a `Request.Entry` with a successful request * value. * * @see {@link complete} for completing an entry with a prebuilt `Exit` * @see {@link completeEffect} for completing an entry from an effect result * @see {@link fail} for completing an entry with a typed failure * @see {@link failCause} for completing an entry with a failure `Cause` * * @stability stable * @category completion * @since 2.0.0 */ (self: Entry, value: Success): Effect.Effect } = dual( 2, (self: Entry, value: Success): Effect.Effect => complete(self, core.exitSucceed(value) as any) ) /** * A pending request handed to a `RequestResolver`. * * **Details** * * An entry contains the original request, the fiber context needed to run it, * an `uninterruptible` flag used by batching and caching internals, and the * `completeUnsafe` callback used by resolvers to supply the final `Exit`. * * @stability stable * @category models * @since 2.0.0 */ 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 } /** * Creates a `Request.Entry` from its component fields. * * **Details** * * This is a low-level helper for request runtime and resolver infrastructure; * most application code receives entries from a `RequestResolver` instead of * constructing them directly. * * @stability stable * @category constructors * @since 2.0.0 */ 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