1/**
2 * Low-level helpers for making custom values behave like Effects. The module
3 * exposes a prototype builder, an abstract base class, and a mixin that let
4 * domain-specific values, such as service keys or configuration descriptions,
5 * be evaluated by Effect and yielded inside `Effect.gen`.
6 *
7 * @since 4.0.0
8 */
9import type * as Effect from "./Effect.ts"
10import type * as Fiber from "./Fiber.ts"
11import { evaluate, makePrimitiveProto } from "./internal/core.ts"
12
13/**
14 * Create a low-level `Effect` prototype.
15 *
16 * **When to use**
17 *
18 * Use when you need to create a custom Effect-like value without extending a
19 * class, by providing a label and an evaluate function that receives the
20 * current fiber.
21 *
22 * **Details**
23 *
24 * When the effect is evaluated, it calls `evaluate` with the current fiber.
25 *
26 * @see {@link Class} for a class-based approach to defining custom Effect values
27 * @see {@link Mixin} for wrapping an existing class constructor
28 *
29 * @category prototypes
30 * @since 4.0.0
31 */
32export const Prototype = <A extends Effect.Effect<any, any, any>>(options: {
33 readonly label: string
34 readonly evaluate: (
35 this: A,
36 fiber: Fiber.Fiber<any, any>
37 ) => Effect.Effect<Effect.Success<A>, Effect.Error<A>, Effect.Services<A>>
38}): Effect.Effect<Effect.Success<A>, Effect.Error<A>, Effect.Services<A>> =>
39 makePrimitiveProto({
40 op: options.label,
41 [evaluate]: options.evaluate
42 }) as any
43
44const proto = Prototype<Class<any, any, any>>({
45 label: "Effectable",
46 evaluate(_) {
47 return this.asEffect()
48 }
49})
50
51const Base: new<A, E, R>() => Effect.Effect<A, E, R> = (() => {
52 const Base = function() {}
53 Base.prototype = proto
54 return Base as any
55})()
56
57/**
58 * Provides an abstract class that can be extended to create an `Effect`.
59 *
60 * **When to use**
61 *
62 * Use as an abstract base class to define custom classes whose instances behave
63 * as `Effect` values.
64 *
65 * @see {@link Prototype} for a lower-level primitive approach to creating custom Effect-like values without a class
66 * @see {@link Mixin} for wrapping an existing class constructor
67 * @category constructors
68 * @since 2.0.0
69 */
70export abstract class Class<A, E = never, R = never> extends Base<A, E, R> {
71 abstract asEffect(): Effect.Effect<A, E, R>
72}
73
74type AsEffectReturn<Self> = Self extends {
75 asEffect(): infer A extends Effect.Effect<any, any, any>
76} ? A
77 : never
78
79declare abstract class MixinBase extends Class<any, any, any> {
80 constructor(...args: ReadonlyArray<any>)
81 override readonly [Effect.TypeId]: AsEffectReturn<this>[Effect.TypeId]
82 override [Symbol.iterator](): Effect.EffectIterator<AsEffectReturn<this>>
83}
84
85/**
86 * Returns a subclass of the provided class that inserts the Effect prototype
87 * into the inheritance chain.
88 *
89 * **When to use**
90 *
91 * Use to make instances of an existing class behave as `Effect` values without
92 * extending {@link Class} or modifying the original prototype.
93 *
94 * **Details**
95 *
96 * Pass the class to wrap, then implement `asEffect` on the final class. The
97 * returned class is abstract, and the success, error, and service types are
98 * inferred from the concrete `asEffect` return type. Concrete and abstract base
99 * classes are supported. Constructor parameters and instance members are
100 * preserved, except that Effect's prototype members shadow base prototype
101 * members with the same name: `pipe`, `toString`, `toJSON`, `[Symbol.iterator]`,
102 * and `[Symbol.for("nodejs.util.inspect.custom")]`.
103 *
104 * **Example** (Evaluating a mixed-in class)
105 *
106 * ```ts import.meta.vitest
107 * import { Effect, Effectable } from "effect"
108 *
109 * class Box {
110 * constructor(readonly value: number) {}
111 * }
112 *
113 * class EffectBox extends Effectable.Mixin(Box) {
114 * asEffect() {
115 * return Effect.succeed(this.value)
116 * }
117 * }
118 *
119 * const box = new EffectBox(2)
120 * Effect.isEffect(box) // => true
121 * await Effect.runPromise(box) // => 2
122 * ```
123 *
124 * @see {@link Prototype} for a lower-level primitive approach to creating custom Effect-like values without a class
125 * @see {@link Class} for a base constructor to extend
126 * @category constructors
127 * @since 4.0.0
128 */
129export const Mixin = <TBase extends abstract new(...args: ReadonlyArray<any>) => object>(
130 klass: TBase
131): TBase & typeof MixinBase => {
132 abstract class Mixed extends klass {
133 abstract asEffect(): Effect.Effect<any, any, any>
134 }
135 Object.defineProperties(Mixed.prototype, Object.getOwnPropertyDescriptors(proto))
136 return Mixed as TBase & typeof MixinBase
137}