diff --git a/packages/infra/package.json b/packages/infra/package.json index 8e4ad89f8..04faab96c 100644 --- a/packages/infra/package.json +++ b/packages/infra/package.json @@ -365,6 +365,10 @@ "types": "./dist/logger/shared.d.ts", "default": "./dist/logger/shared.js" }, + "./pipegen": { + "types": "./dist/pipegen.d.ts", + "default": "./dist/pipegen.js" + }, "./rateLimit": { "types": "./dist/rateLimit.d.ts", "default": "./dist/rateLimit.js" diff --git a/packages/infra/src/pipegen.ts b/packages/infra/src/pipegen.ts new file mode 100644 index 000000000..19c4c1baf --- /dev/null +++ b/packages/infra/src/pipegen.ts @@ -0,0 +1,96 @@ +/* eslint-disable unused-imports/no-unused-vars */ +/* eslint-disable prefer-rest-params */ +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +import { Effect } from "effect-app" +import { type YieldWrap } from "effect/Utils" + +// inspired by Effect.fnUntraced +export type FromGenToEffect = + & Effect.Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [YieldWrap>] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [YieldWrap>] ? R + : never + > + & {} // so it computes + +// inspired by Effect.fnUntraced +export type EffectifyIfGen = A extends + Generator>, infer AEff, never> ? FromGenToEffect + : A extends () => Generator>, infer AEff, never> + ? FromGenToEffect + : A + +export function pipeGen( + a: A +): EffectifyIfGen +export function pipeGen( + a: A, + ab: (_: EffectifyIfGen) => B +): EffectifyIfGen +export function pipeGen( + a: A, + ab: (_: EffectifyIfGen) => B, + bc: (_: EffectifyIfGen) => C +): EffectifyIfGen +export function pipeGen( + a: A, + ab: (value: EffectifyIfGen) => B, + bc: (value: EffectifyIfGen) => C, + cd: (value: EffectifyIfGen) => D +): EffectifyIfGen +export function pipeGen( + a: A, + ab: (value: EffectifyIfGen) => B, + bc: (value: EffectifyIfGen) => C, + cd: (value: EffectifyIfGen) => D, + de: (value: EffectifyIfGen) => E +): EffectifyIfGen +export function pipeGen( + a: A, + ab: (value: EffectifyIfGen) => B, + bc: (value: EffectifyIfGen) => C, + cd: (value: EffectifyIfGen) => D, + de: (value: EffectifyIfGen) => E, + ef: (value: EffectifyIfGen) => F +): EffectifyIfGen +export function pipeGen( + a: A, + ab: (value: EffectifyIfGen) => B, + bc: (value: EffectifyIfGen) => C, + cd: (value: EffectifyIfGen) => D, + de: (value: EffectifyIfGen) => E, + ef: (value: EffectifyIfGen) => F, + fg: (value: EffectifyIfGen) => G +): EffectifyIfGen +export function pipeGen( + a: A, + ab: (value: EffectifyIfGen) => B, + bc: (value: EffectifyIfGen) => C, + cd: (value: EffectifyIfGen) => D, + de: (value: EffectifyIfGen) => E, + ef: (value: EffectifyIfGen) => F, + fg: (value: EffectifyIfGen) => G, + gh: (value: EffectifyIfGen) => H +): EffectifyIfGen +export function pipeGen( + a: unknown, + _ab?: Function, + _bc?: Function, + _cd?: Function, + _de?: Function, + _ef?: Function, + _fg?: Function, + _gh?: Function +): unknown { + let ret = (a as any)[Symbol.toStringTag] === "GeneratorFunction" ? Effect.fnUntraced(a as any)() : a + for (let i = 1; i < arguments.length; i++) { + ret = (arguments[i][Symbol.toStringTag] === "GeneratorFunction" ? Effect.fnUntraced(arguments[i]) : arguments[i])( + ret + ) + } + return ret +} diff --git a/packages/infra/test/pipegen.test.ts b/packages/infra/test/pipegen.test.ts new file mode 100644 index 000000000..e96f07ddd --- /dev/null +++ b/packages/infra/test/pipegen.test.ts @@ -0,0 +1,57 @@ +import { expect, expectTypeOf, it } from "@effect/vitest" +import { Effect } from "effect-app" +import { pipeGen } from "../src/pipegen.js" + +const test = pipeGen(function*() { + return 5 +}) + +it( + "works", + Effect.fnUntraced(function*() { + const res = pipeGen( + 19, + (n) => n * 10, + function*(n) { + return yield* Effect.succeed(n / 2) + }, + Effect.map((n) => String(n + 1)) + ) + expectTypeOf(res).toEqualTypeOf>() + expect(yield* res).toEqual("96") + + const res2 = pipeGen( + function*() { + return yield* Effect.succeed(8) + }, + Effect.map((n) => String(n + 1)) + ) + expectTypeOf(res2).toEqualTypeOf>() + expect(yield* res2).toEqual("9") + + const res3 = pipeGen( + function*() { + return yield* Effect.succeed(8) + }, + Effect.map((n) => String(n + 1)), + function*(e) { + return (yield* e).repeat(2).length > 3 + } + ) + expectTypeOf(res3).toEqualTypeOf>() + expect(yield* res3).toEqual(false) + + const res4 = pipeGen( + function*() { + // note: no yield* of effects here + return 8 + }, + Effect.map((n) => String(n + 1)), + function*(e) { + return (yield* e).repeat(2).length > 3 + } + ) + expectTypeOf(res4).toEqualTypeOf>() + expect(yield* res4).toEqual(false) + }, Effect.runPromise) +)