1/**
2 * Typed request values for data loading with `Effect.request`.
3 *
4 * A request describes one logical piece of work without performing it. It
5 * records the success type, typed error, service requirements, and fields a
6 * resolver needs to complete the request. Requests are paired with
7 * `RequestResolver`, which performs backend-specific loading and completes each
8 * pending request entry with a success, failure, cause, exit, or effect.
9 *
10 * @since 2.0.0
11 */
12import type * as Cause from "./Cause.ts"
13import type * as Context from "./Context.ts"
14import type * as Effect from "./Effect.ts"
15import * as Equal from "./Equal.ts"
16import type * as Exit from "./Exit.ts"
17import { dual } from "./Function.ts"
18import * as core from "./internal/core.ts"
19import * as internalEffect from "./internal/effect.ts"
20import * as InternalRecord from "./internal/record.ts"
21import { hasProperty } from "./Predicate.ts"
22import type * as Types from "./Types.ts"
23
24const TypeId = "~effect/Request"
25
26/**
27 * A `Request<A, E, R>` is a request from a data source for a value of type `A`
28 * that may fail with an `E` and have requirements of type `R`.
29 *
30 * **Example** (Defining typed requests)
31 *
32 * ```ts import.meta.vitest
33 * import type { Request } from "effect"
34 *
35 * // Define a request that fetches a user by ID
36 * interface GetUser extends Request.Request<string, Error> {
37 * readonly _tag: "GetUser"
38 * readonly id: number
39 * }
40 *
41 * // Define a request that fetches all users
42 * interface GetAllUsers extends Request.Request<ReadonlyArray<string>, Error> {
43 * readonly _tag: "GetAllUsers"
44 * }
45 *
46 * ```
47 *
48 * @category models
49 * @since 2.0.0
50 */
51export interface Request<out A, out E = never, out R = never> extends Variance<A, E, R> {}
52
53/**
54 * Alias for any `Request`, regardless of its success, error, or service
55 * requirements.
56 *
57 * **When to use**
58 *
59 * Use as a generic constraint for APIs that accept any request while preserving
60 * each concrete request's success, error, and service types.
61 *
62 * @see {@link Request} for the request interface
63 * @see {@link Success} for extracting a request's success type
64 * @see {@link Error} for extracting a request's error type
65 * @see {@link Services} for extracting a request's service requirements
66 * @see {@link Result} for the exit type produced by completing a request
67 *
68 * @category utility types
69 * @since 4.0.0
70 */
71export type Any = Request<any, any, any>
72
73/**
74 * Variance marker carried by every `Request`.
75 *
76 * **Details**
77 *
78 * This marker preserves the success, error, and service requirement types for
79 * Effect's type-level machinery. Users normally get it by extending `Request`.
80 *
81 * @category models
82 * @since 2.0.0
83 */
84export interface Variance<out A, out E, out R> {
85 readonly [TypeId]: {
86 readonly _A: Types.Covariant<A>
87 readonly _E: Types.Covariant<E>
88 readonly _R: Types.Covariant<R>
89 }
90}
91
92/**
93 * The constructor type returned by `Request.of` and `Request.tagged`.
94 *
95 * **Details**
96 *
97 * The constructor accepts the request's data fields, excluding request variance
98 * fields and any fields already supplied by the constructor such as `_tag`, and
99 * returns a value of the request type.
100 *
101 * **Example** (Using generated request constructors)
102 *
103 * ```ts import.meta.vitest
104 * import { Request } from "effect"
105 *
106 * interface GetUser extends Request.Request<string, Error> {
107 * readonly _tag: "GetUser"
108 * readonly id: number
109 * }
110 *
111 * // Constructor type is used internally by Request.of() and Request.tagged()
112 * const GetUser = Request.tagged<GetUser>("GetUser")
113 * const request = GetUser({ id: 123 })
114 *
115 * request._tag // => "GetUser"
116 * request.id // => 123
117 * ```
118 *
119 * @category models
120 * @since 2.0.0
121 */
122export interface Constructor<R extends Request<any, any, any>, T extends keyof R = never> {
123 (args: Types.VoidIfEmpty<Types.Simplify<Omit<R, T | keyof (Variance<any, any, any>)>>>): R
124}
125
126/**
127 * A utility type to extract the error type from a `Request`.
128 *
129 * **Example** (Extracting a request error type)
130 *
131 * ```ts import.meta.vitest
132 * import type { Request } from "effect"
133 *
134 * interface GetUser extends Request.Request<string, Error> {
135 * readonly id: number
136 * }
137 *
138 * // Extract the error type from a Request using the utility
139 * type UserError = Request.Error<GetUser> // Error
140 *
141 * ```
142 *
143 * @category utility types
144 * @since 2.0.0
145 */
146export type Error<T extends Request<any, any, any>> = [T] extends [Request<infer _A, infer _E, infer _R>] ? _E : never
147
148/**
149 * A utility type to extract the value type from a `Request`.
150 *
151 * **Example** (Extracting a request success type)
152 *
153 * ```ts import.meta.vitest
154 * import type { Request } from "effect"
155 *
156 * interface GetUser extends Request.Request<string, Error> {
157 * readonly _tag: "GetUser"
158 * readonly id: number
159 * }
160 *
161 * // Extract the success type from a Request using the utility
162 * type UserSuccess = Request.Success<GetUser> // string
163 *
164 * ```
165 *
166 * @category utility types
167 * @since 2.0.0
168 */
169export type Success<T extends Request<any, any, any>> = [T] extends [Request<infer _A, infer _E, infer _R>] ? _A
170 : never
171
172/**
173 * A utility type to extract the requirements type from a `Request`.
174 *
175 * @category utility types
176 * @since 4.0.0
177 */
178export type Services<T extends Request<any, any, any>> = [T] extends [Request<infer _A, infer _E, infer _R>] ? _R
179 : never
180
181/**
182 * A utility type to extract the result type from a `Request`.
183 *
184 * **Example** (Extracting a request result type)
185 *
186 * ```ts import.meta.vitest
187 * import type { Request } from "effect"
188 *
189 * interface GetUser extends Request.Request<string, Error> {
190 * readonly _tag: "GetUser"
191 * readonly id: number
192 * }
193 *
194 * // Extract the result type from a Request using the utility
195 * type UserResult = Request.Result<GetUser> // Exit.Exit<string, Error>
196 *
197 * ```
198 *
199 * @category utility types
200 * @since 2.0.0
201 */
202export type Result<T extends Request<any, any, any>> = T extends Request<infer A, infer E, infer _R> ? Exit.Exit<A, E>
203 : never
204
205const requestVariance = Equal.byReferenceUnsafe({
206 /* c8 ignore next */
207 _E: (_: never) => _,
208 /* c8 ignore next */
209 _A: (_: never) => _,
210 /* c8 ignore next */
211 _R: (_: never) => _
212})
213
214/**
215 * Prototype used by Effect's request constructors.
216 *
217 * **Details**
218 *
219 * This low-level value provides the structural request marker for values
220 * created by `Request.of`, `Request.tagged`, `Request.Class`, and
221 * `Request.TaggedClass`. Most users should use those constructors instead of
222 * interacting with the prototype directly.
223 *
224 * @category prototypes
225 * @since 4.0.0
226 */
227export const RequestPrototype: Request<any, any, any> = {
228 ...core.StructuralProto,
229 [TypeId]: requestVariance
230}
231
232/**
233 * Checks whether a value is a `Request`.
234 *
235 * **Example** (Checking request values)
236 *
237 * ```ts import.meta.vitest
238 * import { Request } from "effect"
239 *
240 * declare const User: unique symbol
241 * declare const UserNotFound: unique symbol
242 * type User = typeof User
243 * type UserNotFound = typeof UserNotFound
244 *
245 * interface GetUser extends Request.Request<User, UserNotFound> {
246 * readonly _tag: "GetUser"
247 * readonly id: string
248 * }
249 * const GetUser = Request.tagged<GetUser>("GetUser")
250 *
251 * const request = GetUser({ id: "123" })
252 * Request.isRequest(request) // => true
253 * Request.isRequest("not a request") // => false
254 * ```
255 *
256 * @category guards
257 * @since 2.0.0
258 */
259export const isRequest = (u: unknown): u is Request<unknown, unknown, unknown> => hasProperty(u, TypeId)
260
261/**
262 * Creates a constructor function for a specific Request type.
263 *
264 * **Example** (Creating untagged request constructors)
265 *
266 * ```ts import.meta.vitest
267 * import { Request } from "effect"
268 *
269 * declare const UserProfile: unique symbol
270 * declare const ProfileError: unique symbol
271 * type UserProfile = typeof UserProfile
272 * type ProfileError = typeof ProfileError
273 *
274 * interface GetUserProfile extends Request.Request<UserProfile, ProfileError> {
275 * readonly id: string
276 * readonly includeSettings: boolean
277 * }
278 *
279 * const GetUserProfile = Request.of<GetUserProfile>()
280 *
281 * const request = GetUserProfile({
282 * id: "user-123",
283 * includeSettings: true
284 * })
285 *
286 * request.id // => "user-123"
287 * request.includeSettings // => true
288 * ```
289 *
290 * @category constructors
291 * @since 2.0.0
292 */
293export const of = <R extends Request<any, any, any>>(): Constructor<R> => (args) =>
294 Object.setPrototypeOf({ ...(args as R) }, RequestPrototype)
295
296/**
297 * Creates a constructor function for a tagged Request type. The tag is automatically
298 * added to the request, making it useful for discriminated unions.
299 *
300 * **Example** (Creating tagged request constructors)
301 *
302 * ```ts import.meta.vitest
303 * import { Request } from "effect"
304 *
305 * declare const User: unique symbol
306 * declare const UserNotFound: unique symbol
307 * declare const Post: unique symbol
308 * declare const PostNotFound: unique symbol
309 * type User = typeof User
310 * type UserNotFound = typeof UserNotFound
311 * type Post = typeof Post
312 * type PostNotFound = typeof PostNotFound
313 *
314 * interface GetUser extends Request.Request<User, UserNotFound> {
315 * readonly _tag: "GetUser"
316 * readonly id: string
317 * }
318 *
319 * interface GetPost extends Request.Request<Post, PostNotFound> {
320 * readonly _tag: "GetPost"
321 * readonly id: string
322 * }
323 *
324 * const GetUser = Request.tagged<GetUser>("GetUser")
325 * const GetPost = Request.tagged<GetPost>("GetPost")
326 *
327 * const userRequest = GetUser({ id: "user-123" })
328 * const postRequest = GetPost({ id: "post-456" })
329 *
330 * // _tag is automatically set
331 * Array.of(userRequest._tag, postRequest._tag) // => ["GetUser", "GetPost"]
332 * ```
333 *
334 * @category constructors
335 * @since 2.0.0
336 */
337export const tagged = <R extends Request<any, any, any> & { _tag: string }>(
338 tag: R["_tag"]
339): Constructor<R, "_tag"> =>
340(args) => {
341 return Object.setPrototypeOf({ ...(args as R), _tag: tag }, RequestPrototype)
342}
343
344/**
345 * Defines request types with TypeScript classes.
346 *
347 * **Details**
348 *
349 * Subclasses pass their data fields to `super`, and instances are marked as
350 * `Request` values while retaining the provided readonly fields.
351 *
352 * **Example** (Defining request classes)
353 *
354 * ```ts import.meta.vitest
355 * import { Request } from "effect"
356 *
357 * class GetUser extends Request.Class<{ id: number }, string, Error> {
358 * constructor(readonly id: number) {
359 * super({ id })
360 * }
361 * }
362 *
363 * const getUserRequest = new GetUser(123)
364 * getUserRequest.id // => 123
365 * ```
366 *
367 * @category constructors
368 * @since 2.0.0
369 */
370export const Class: new<A extends Record<string, any>, Success, Error = never, Context = never>(
371 args: Types.Equals<Omit<A, keyof Request<unknown, unknown>>, {}> extends true ? void
372 : { readonly [P in keyof A as P extends keyof Request<any, any, any> ? never : P]: A[P] }
373) => Request<Success, Error, Context> & Readonly<A> = (function() {
374 function Class(this: object, args: object | undefined) {
375 if (args) {
376 InternalRecord.assignProperties(this, args)
377 }
378 }
379 Class.prototype = RequestPrototype
380 return Class as any
381})()
382
383/**
384 * Creates a class constructor for requests with a fixed `_tag` field.
385 *
386 * **Details**
387 *
388 * Use this when defining class-based request types that should participate in
389 * tagged unions or tag-based request resolvers.
390 *
391 * **Example** (Defining tagged request classes)
392 *
393 * ```ts import.meta.vitest
394 * import { Request } from "effect"
395 *
396 * class GetUserById
397 * extends Request.TaggedClass("GetUserById")<{ id: number }, string, Error>
398 * {}
399 *
400 * const request = new GetUserById({ id: 123 })
401 *
402 * request._tag // => "GetUserById"
403 * request.id // => 123
404 * ```
405 *
406 * @category constructors
407 * @since 2.0.0
408 */
409export const TaggedClass = <Tag extends string>(
410 tag: Tag
411): new<A extends Record<string, any>, Success, Error = never, Services = never>(
412 args: Types.Equals<Omit<A, keyof Request<unknown, unknown>>, {}> extends true ? void
413 : { readonly [P in keyof A as P extends "_tag" | keyof Request<any, any, any> ? never : P]: A[P] }
414) => Request<Success, Error, Services> & Readonly<A> & { readonly _tag: Tag } => {
415 return class TaggedClass extends Class<any, any, any> {
416 readonly _tag = tag
417 } as any
418}
419
420/**
421 * Completes a request entry with the provided result.
422 *
423 * **When to use**
424 *
425 * Use when you need to finish a `Request.Entry` with a prebuilt final `Exit`
426 * result.
427 *
428 * @see {@link completeEffect} for completing an entry from an effect that may succeed or fail
429 * @see {@link succeed} for completing an entry with a successful value
430 * @see {@link fail} for completing an entry with a typed failure
431 * @see {@link failCause} for completing an entry with a failure `Cause`
432 *
433 * @category completion
434 * @since 2.0.0
435 */
436export const complete: {
437 <A extends Any>(result: Result<A>): (self: Entry<A>) => Effect.Effect<void>
438 <A extends Any>(self: Entry<A>, result: Result<A>): Effect.Effect<void>
439} = dual(
440 2,
441 <A extends Any>(self: Entry<A>, result: Result<A>): Effect.Effect<void> =>
442 internalEffect.sync(() => self.completeUnsafe(result))
443)
444
445/**
446 * Completes a request entry with the result of an effect.
447 *
448 * **When to use**
449 *
450 * Use to finish a `Request.Entry` by running an effect whose success or typed
451 * failure should become the request result.
452 *
453 * **Details**
454 *
455 * If the effect succeeds, the entry is completed successfully with its value.
456 * If the effect fails, the entry is completed with that failure.
457 *
458 * **Gotchas**
459 *
460 * The returned effect itself does not fail with the request error.
461 *
462 * @see {@link complete} for completing an entry with a prebuilt `Exit`
463 * @see {@link succeed} for completing an entry with a successful value
464 * @see {@link fail} for completing an entry with a typed failure
465 * @see {@link failCause} for completing an entry with a failure `Cause`
466 *
467 * @category completion
468 * @since 2.0.0
469 */
470export const completeEffect: {
471 <A extends Any, R>(effect: Effect.Effect<Success<A>, Error<A>, R>): (self: Entry<A>) => Effect.Effect<void, never, R>
472 <A extends Any, R>(self: Entry<A>, effect: Effect.Effect<Success<A>, Error<A>, R>): Effect.Effect<void, never, R>
473} = dual(
474 2,
475 <A extends Any, R>(self: Entry<A>, effect: Effect.Effect<Success<A>, Error<A>, R>): Effect.Effect<void, never, R> =>
476 internalEffect.matchEffect(effect, {
477 onFailure: (error) => complete(self, core.exitFail(error) as any),
478 onSuccess: (value) => complete(self, core.exitSucceed(value) as any)
479 })
480)
481
482/**
483 * Completes a request entry with a typed failure.
484 *
485 * **When to use**
486 *
487 * Use to report a request-specific typed error while implementing a
488 * `RequestResolver`.
489 *
490 * @see {@link failCause} for completing an entry with a full `Cause`
491 * @see {@link complete} for completing an entry with an existing `Exit`
492 * @see {@link completeEffect} for completing an entry from an effect result
493 * @see {@link succeed} for completing an entry successfully
494 *
495 * @category completion
496 * @since 2.0.0
497 */
498export const fail: {
499 <A extends Any>(error: Error<A>): (self: Entry<A>) => Effect.Effect<void>
500 <A extends Any>(self: Entry<A>, error: Error<A>): Effect.Effect<void>
501} = dual(
502 2,
503 <A extends Any>(self: Entry<A>, error: Error<A>): Effect.Effect<void> => complete(self, core.exitFail(error) as any)
504)
505
506/**
507 * Completes a request entry with a failure `Cause`.
508 *
509 * **When to use**
510 *
511 * Use when you need a `RequestResolver` to complete an entry with structured
512 * cause information rather than only the request's typed error value.
513 *
514 * @see {@link fail} for completing an entry with a typed error value
515 * @see {@link complete} for completing an entry with an existing `Exit`
516 * @see {@link completeEffect} for completing an entry from an effect result
517 * @see {@link succeed} for completing an entry successfully
518 *
519 * @category completion
520 * @since 2.0.0
521 */
522export const failCause: {
523 <A extends Any>(cause: Cause.Cause<Error<A>>): (self: Entry<A>) => Effect.Effect<void>
524 <A extends Any>(self: Entry<A>, cause: Cause.Cause<Error<A>>): Effect.Effect<void>
525} = dual(
526 2,
527 <A extends Any>(self: Entry<A>, cause: Cause.Cause<Error<A>>): Effect.Effect<void> =>
528 complete(self, core.exitFailCause(cause) as any)
529)
530
531/**
532 * Completes a request entry successfully with the supplied value.
533 *
534 * **When to use**
535 *
536 * Use when you need to finish a `Request.Entry` with a successful request
537 * value.
538 *
539 * @see {@link complete} for completing an entry with a prebuilt `Exit`
540 * @see {@link completeEffect} for completing an entry from an effect result
541 * @see {@link fail} for completing an entry with a typed failure
542 * @see {@link failCause} for completing an entry with a failure `Cause`
543 *
544 * @category completion
545 * @since 2.0.0
546 */
547export const succeed: {
548 <A extends Any>(value: Success<A>): (self: Entry<A>) => Effect.Effect<void>
549 <A extends Any>(self: Entry<A>, value: Success<A>): Effect.Effect<void>
550} = dual(
551 2,
552 <A extends Any>(self: Entry<A>, value: Success<A>): Effect.Effect<void> =>
553 complete(self, core.exitSucceed(value) as any)
554)
555
556/**
557 * A pending request handed to a `RequestResolver`.
558 *
559 * **Details**
560 *
561 * An entry contains the original request, the fiber context needed to run it,
562 * an `uninterruptible` flag used by batching and caching internals, and the
563 * `completeUnsafe` callback used by resolvers to supply the final `Exit`.
564 *
565 * @category models
566 * @since 2.0.0
567 */
568export interface Entry<out R> {
569 readonly request: R
570 readonly context: Context.Context<
571 [R] extends [Request<infer _A, infer _E, infer _R>] ? _R : never
572 >
573 uninterruptible: boolean
574 completeUnsafe(
575 exit: Exit.Exit<
576 [R] extends [Request<infer _A, infer _E, infer _R>] ? _A : never,
577 [R] extends [Request<infer _A, infer _E, infer _R>] ? _E : never
578 >
579 ): void
580}
581
582/**
583 * Creates a `Request.Entry` from its component fields.
584 *
585 * **Details**
586 *
587 * This is a low-level helper for request runtime and resolver infrastructure;
588 * most application code receives entries from a `RequestResolver` instead of
589 * constructing them directly.
590 *
591 * @category constructors
592 * @since 2.0.0
593 */
594export const makeEntry = <R>(options: {
595 readonly request: R
596 readonly context: Context.Context<
597 [R] extends [Request<infer _A, infer _E, infer _R>] ? _R : never
598 >
599 readonly uninterruptible: boolean
600 readonly completeUnsafe: (
601 exit: Exit.Exit<
602 [R] extends [Request<infer _A, infer _E, infer _R>] ? _A : never,
603 [R] extends [Request<infer _A, infer _E, infer _R>] ? _E : never
604 >
605 ) => void
606}): Entry<R> => options