npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

ts-flowgen

v2.0.0

Published

Type-safe error management using generators. Inspired by [EffectTS](https://effect.website/) and [typescript-result](https://github.com/everweij/typescript-result)

Downloads

30

Readme

flowgen

Type-safe error management using generators. Inspired by EffectTS and typescript-result

Usage

Without flowgen

You throw your errors and thus relies on untyped goto-like pattern:

async function dependency1(a: number, b: number): number {
  if (b === 0) {
    throw new Error(`Invalid denominator: ${b}`);
  }

  return a / b;
}

async function dependency2(a: number, b: number): number {
  if (b === 0) {
    throw new Error(`Invalid denominator: ${b}`);
  }

  return a / b;
}

async function main(userInput: number) {
  try {
    const result = await dependency1(10, userInput);
    const result2 = await dependency2(20, userInput);

    console.log(result + result2);
  } catch (error /* error is unknown */) {
    console.log("Some error happened", error);
  }
}

With Result pattern

You have verbose but type-safe code. For every result you have to check if the result is an error:

type Result<Value, Error> =
  | { ok: true; value: Value }
  | { ok: false; error: Error };

async function dependency1(
  a: number,
  b: number
): Promise<Result<number, { name: "valueError"; message: string }>> {
  if (b === 0) {
    return {
      ok: false,
      error: { name: "valueError", message: `Invalid denominator: ${b}` },
    };
  }

  return { ok: true, value: a / b };
}

async function dependency2(
  a: number,
  b: number
): Promise<Result<number, { name: "valueError"; message: string }>> {
  if (b === 0) {
    return {
      ok: false,
      error: { name: "valueError", message: `Invalid denominator: ${b}` },
    };
  }

  return { ok: true, value: a / b };
}

async function main(userInput: number) {
  const result1 = await dependency1(10, userInput);

  // for every method you call, you need to infer `Result` to a successful state
  if (result1.ok == false) {
    console.log(
      "Some error happened",
      result1.error /* error is properly typed */
    );
    return;
  }

  const result2 = await dependency2(20, userInput);

  // this means lots of boilerplate code to handle errors
  if (result2.ok == false) {
    console.log(
      "Some error happened",
      result2.error /* error is properly typed */
    );
    return;
  }

  console.log(result1.value + result2.value);
}

With flowgen

You get automatic typing for errors and returns:

async function* dependency1(a: number, b: number) {
  if (b === 0) {
    yield { type: "valueError", message: "Invalid denominator: 0" } as const;
  }

  return a / b;
}

async function* dependency2(a: number, b: number) {
  if (b === 0) {
    yield { type: "valueError", message: "Invalid denominator: 0" } as const;
  }

  return a / b;
}

async function main(userInput: number) {
  const result = await flow(async function* () {
    // yield intermediate method which unwraps the value, no chains of `if error early return`
    const value1 = yield* dependency1(10, userInput); // value is number
    const value2 = yield* dependency2(10, userInput); // value is number

    return value1 + value2;
  });

  // only one error management per flow with an exhaustive switch
  if (result.ok === false) {
    /* result.error is properly typed */
    switch (result.error.type) {
      case "valueError":
        console.log("Some error happened", result.error);
        return;
      default:
        never();
    }
  }

  console.log(result.value);
}

The only drawbacks are:

  1. You have to wrap external libraries if you want to add support for AsyncGenerators
  2. You need to yield errors using as const (or type the return of generators) since TypeScript will infer a poorly intersection type instead (instead of a union)

You can find an example of how to use flowgen in src/__tests__/complete-example.test.ts

API

flow(generator)

async function flow<Error, Value>(
  generator: () => Generator<Error, Value> | AsyncGenerator<Error, Value>
): Promise<{ ok: true; value: Value } | { ok: false; error: Error }>;

// or the wrapper one:
function wrapFlow<Parameters extends unknown[], Error, Value>(
  generator: (
    ...args: Parameters
  ) => Generator<Error, Value> | AsyncGenerator<Error, Value>
): (
  ...args: Parameters
) => Promise<{ ok: true; value: Value } | { ok: false; error: Error }>;

This method turns a generator into a promise. Useful as entrypoint before using generators. Inside the generator, always yield* other generators.

Example:

const result = await flow(async function* () {
  const a = yield* serviceA.methodA();
  const b = yield* serviceB.methodB(a);
  const c = yield* serviceC.methodC(b);

  return c;
});

if (result.ok === false) {
  // deal with result.error which is an union of errors yielded by methodA, methodB or methodC
}

// deal with result.value which is equal to `c`

Example with wrapFlow:

async function* someGenerator(value: number) {
  const a = yield* serviceA.methodA(value);
  const b = yield* serviceB.methodB(a);
  const c = yield* serviceC.methodC(b);

  return c;
}

const result = await wrapFlow(someGenerator)(42);

if (result.ok === false) {
  // deal with result.error which is an union of errors yielded by methodA, methodB or methodC
}

// deal with result.value which is equal to `c`

gen(callback)

function gen<Parameters extends unknown[], Error, Value>(
  callback: (...args: Parameters) => Value | Promise<Value>,
  unhandledError: (error: unknown) => Error = (error) => error as Error
): (...args: Parameters) => AsyncGenerator<Error, Value>;

This method turns a sync/async method into a generator.

Example:

import fs from "node:fs/promises";
const readFile = gen(
  async (path: string) => {
    return await fs.readFile(path, "utf-8");
  },
  () => ({ name: "ioError", message: "File not found" })
);

const result = flow(async function* () {
  const file = yield* readFile("file.txt");

  return file;
});

never()

function never(): never;

A never helper. Can be useful when you want to infer a value after yielding an error.

Example:

async function* method(a: 1 | 2): AsyncGenerator<Error, number> {
  if (a === 1) {
    yield new Error(`Invalid denominator: ${a}`);
    never();
  }

  return a; // inferred to 2
}

noop()

function noop(): AsyncGenerator<never, void>;

A noop helper. Can be useful when you want to yield nothing just to please the linter.

Example:

async function* method() {
  yield* noop();

  return 42;
}

identity()

function identity<Value>(value: Value): AsyncGenerator<never, Value>;

A noop helper. Can be useful when you want to yield a value

Example:

async function* method() {
  return yield* identity(42);
}

all()

Similar to Promise.all() for generators

function all<Error, Value>(
  generators: AsyncGenerator<Error, Value>[]
): AsyncGenerator<Error, Value[]>;

Example:

async function* dep1() {
  await setTimeout(50);
  return 1;
}

async function* dep2() {
  await setTimeout(80);
  return 1;
}

const result = flow(async function* () {
  // runs in parallel
  const [a, b] = yield* all([dep1(), dep2()]);
});

race()

Similar to Promise.race() for generators

function race<Error, Value>(
  generators: AsyncGenerator<Error, Value>[]
): AsyncGenerator<Error, Value>;

Example:

async function* dep1() {
  await setTimeout(50);
  return 1;
}

async function* dep2() {
  await setTimeout(80);
  return 2;
}

const result = flow(async function* () {
  // runs in parallel
  const a = yield* race([dep1(), dep2()]);
  // a = 1 since dep1 is faster than dep2
});

timeout()

Helper to make a generator not exceed a specific time

function timeout<Error, Value>(
  timeoutInMs: number,
  generator: AsyncGenerator<Error, Value>
): AsyncGenerator<Error | TimeoutError, Value>;

Example:

async function* dep1() {
  await setTimeout(50);
  return 1;
}

const result = flow(async function* () {
  const a = yield* timeout(100, dep1());
  // a = 1 since dep1 is fast enough

  const b = yield* timeout(10, dep1());
  // this will timeout and result will be a TimeoutError
});

unsafeFlowOrThrow()

Similar to flow but returns the value or throws instead of returning a result. This will mute error type-safety, use it with caution

unsafeFlowOrThrow<Value>(
	callback: () => Generator<unknown, Value> | AsyncGenerator<unknown, Value>,
): Promise<Value>

Example:

async function* method() {
  // this is 100% safe
  const a = yield* identity(1);
  const b = yield* identity(2);

  return a + b;
}

const value = await unsafeFlowOrThrow(method); // value = 3