| 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Effectable from "effect/Effectable"; |
| 4 | |
| 5 | export type EffectClass<Shape, A, Err = never, Req = never> = Effect.Effect< |
| 6 | A, |
| 7 | Err, |
| 8 | Req |
| 9 | > & { |
| 10 | new (_: never): Shape; |
| 11 | }; |
| 12 | |
| 13 | export const effectClass: { |
| 14 | <A, Err = never, Req = never>( |
| 15 | impl: Effect.Effect<A, Err, Req>, |
| 16 | ): EffectClass<A, A, Err, Req>; |
| 17 | <Shape>(): <A, Err = never, Req = never>( |
| 18 | impl: Effect.Effect<A, Err, Req>, |
| 19 | ) => EffectClass<Shape, A, Err, Req>; |
| 20 | } = ((impl?: any) => |
| 21 | impl === undefined |
| 22 | ? (innerImpl: any) => effectClass(innerImpl) |
| 23 | : (Object.assign( |
| 24 | class {}, |
| 25 | // Spreading the Effect prototype onto the class (static side) makes the |
| 26 | // class itself a real Effect — `Effect.isEffect(X)` is true, so |
| 27 | // `Effect.all([X])` / `Effect.forEach` work — and subclasses |
| 28 | // (`class Y extends effectClass(impl)`) inherit the protocol statically. |
| 29 | Effectable.Prototype({ |
| 30 | label: "alchemy/EffectClass", |
| 31 | evaluate: () => impl, |
| 32 | }), |
| 33 | ) as unknown as EffectClass<any, any, any, any>)) as any; |
| 34 | |
| 35 | export const taggedFunction = < |
| 36 | Tag extends Context.ServiceClass<any, any, any>, |
| 37 | Fn extends (...args: any[]) => any, |
| 38 | >( |
| 39 | tag: Tag, |
| 40 | fn: Fn, |
| 41 | ): Tag & Fn => { |
| 42 | // The Proxy below forwards every Effect-protocol key to `tag` (already an |
| 43 | // Effect), so `asEffect`/`[Symbol.iterator]`/`pipe` need no explicit |
| 44 | // override — only `toString` diverges. |
| 45 | const overrides = { |
| 46 | toString: () => `${tag.toString()}.${fn.name}`, |
| 47 | }; |
| 48 | |
| 49 | return new Proxy(fn, { |
| 50 | get: (target, prop, receiver) => |
| 51 | Reflect.has(overrides, prop) |
| 52 | ? Reflect.get(overrides, prop, receiver) |
| 53 | : Reflect.has(target, prop) |
| 54 | ? Reflect.get(target, prop, receiver) |
| 55 | : Reflect.get(tag as object, prop, tag), |
| 56 | has: (target, prop) => |
| 57 | Reflect.has(overrides, prop) || |
| 58 | Reflect.has(target, prop) || |
| 59 | Reflect.has(tag as object, prop), |
| 60 | }) as Tag & Fn; |
| 61 | }; |
| 62 | |
| 63 | export const isYieldableEffect = ( |
| 64 | value: unknown, |
| 65 | ): value is Effect.Effect<unknown, unknown, unknown> => |
| 66 | Effect.isEffect(value) && |
| 67 | typeof (value as any as { [Symbol.iterator]?: unknown })[Symbol.iterator] === |
| 68 | "function"; |
| 69 | |
| 70 | export type YieldableEffectLike<A = unknown, E = unknown, R = unknown> = |
| 71 | | Effect.Effect<A, E, R> |
| 72 | | { |
| 73 | asEffect: () => Effect.Effect<A, E, R>; |
| 74 | [Symbol.iterator]: () => Iterator<unknown>; |
| 75 | }; |
| 76 | |
| 77 | export const isEffectClassLike = ( |
| 78 | value: unknown, |
| 79 | ): value is YieldableEffectLike => |
| 80 | typeof value === "function" && |
| 81 | typeof (value as { asEffect?: unknown }).asEffect === "function"; |
| 82 | |
| 83 | export const isYieldableEffectLike = ( |
| 84 | value: unknown, |
| 85 | ): value is YieldableEffectLike => |
| 86 | (isYieldableEffect(value) || isEffectClassLike(value)) && |
| 87 | !("~alchemy/Kind" in value); |
| 88 | |
| 89 | export type UnwrapEffect<T> = |
| 90 | T extends Effect.Effect<infer A, any, any> ? A : T; |
| 91 | |
| 92 | export type ToEffectInterface<T> = { |
| 93 | raw: T; |
| 94 | } & { |
| 95 | [K in keyof T]: T[K] extends (...args: any[]) => any |
| 96 | ? (...args: Parameters<T[K]>) => Effect.Effect<Awaited<ReturnType<T[K]>>> |
| 97 | : T[K]; |
| 98 | }; |
| 99 | |
| 100 | export const toEffectInterface = <T extends object>(raw: T) => |
| 101 | ({ |
| 102 | raw, |
| 103 | ...Object.fromEntries( |
| 104 | Object.entries(raw).map(([key, value]) => [ |
| 105 | key, |
| 106 | typeof value === "function" |
| 107 | ? (...args: any[]) => Effect.tryPromise(async () => value(...args)) |
| 108 | : value, |
| 109 | ]), |
| 110 | ), |
| 111 | }) as ToEffectInterface<T>; |