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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vr1/flow-r

v4.0.0

Published

Утилита для конвейерно ориентированного программирования. Подобно монаде Either, даёт обработку ошибок без исключений, сквозную передачу параметров, каскадирование.

Readme

@vr1/flow-r

Утилита для конвейерно ориентированного программирования. Подобно монаде Either, даёт обработку ошибок без исключений. А также добавляет сквозную передачу параметров и каскадирование. Позволяет создавать как синхронные так и асинхронные цепочки/рецепты.

Описание

@vr1/flow-r — это библиотека для построения программ с явным и удобным управлением потоком выполнения, обработкой ошибок без использования исключений (throw/catch), и сквозной передачей параметров. Вдохновлена монадами типа Either/Result.

Позволяет организовывать цепочки вызовов («конвейеры») с автоматическим распространением ошибок и удобным контролем над потоком данных.

Установка

npm install @vr1/flow-r

Основные концепции

  • Ошибки без исключений — обработка ошибок происходит явно через возвращаемые значения, без выбрасывания исключений.
  • Поток данных — параметры и ошибки передаются через весь пайплайн без лишней кухонной логики.
  • Каскадирование — рецепты могут использоваться как шаги для вышестоящих рецептов.

Примеры использования

Базовый пример - путь

import { путь } from "@vr1/flow-r";

function действие1(вход: { ключ1: string }): { объект1: Object } {
  // результирующая функция потребует на вход параметр ключ1
  // в виде { ключ1: string }
}

function действие2(вход: { ключ2: number, объект1: Object }): { объект2: Map<string, string> } {
   // результирующая функция потребует на вход параметр ключ2
   // в дополнение к ключ1, результирующий входной параметр будет { ключ1: string, ключ2: number }
   // несмотря на то, что объект1 тут требуется на вход, в результирующую функцию на вход его подавать не надо
   // действие2 на вход его получит из результата действия1
}

function действие3(вход: { объект1: Object, объект2: Map<string, string> }): { поле3: string } {
    // действие3 получит на вход тот объект1, что вернёт действие1, и тот объект2, что вернёт действие2
    // результирующая функция будет возвращать весь набор параметров, проходящий через цепочку
}

// Завершить цепочку нужно методом выход, и указать набор полей.
const результирующая_функция 
  = путь("результирующая_функция")
    .шаг(действие1)
    .шаг(действие2)
    .шаг(действие3)
    .выход("поле3");

// сигнатура результирующей функции станет такой
type РезультирующаяФункция = (вход: { ключ1: string, ключ2: number }) => { поле3: string } | Ошибка;

Обёртка для пути

Иногда нужно выполнить какой-либо код до и после выполнения всей цепочки шагов. Например, можно использовать обёртку для управления транзакциями в базе данных. Для этого используется метод .обертка().

Обёртка — это функция, которая получает входные данные и callback-функцию для выполнения цепочки шагов. Callback должна быть вызвана, чтобы выполнить остальную цепочку.

import { путь } from "@vr1/flow-r";

async function в_транзакции(вход: { id: number }, вызов: () => Promise<void>): Promise<void> {
  const транзакция = await начни_транзакцию();
  const результат = await вызов(); // Выполняет все шаги цепочки внутри транзакции
  if (это_ошибка(результат)) return await транзакция.rollback();
  await транзакция.commit();
}

const цепочка =
  путь("сохрани_в_бд")
    .обертка(в_транзакции)
    .шаг(обновить_корзину)
    .шаг(обновить пользователя)
    .выход("статус");

await цепочка({ id: 10, ... });

Важные особенности обёртки:

  1. Обёртка получает входные данные — в первый параметр обёртка получит данные с которыми вызвана цепочка, нужно указать те, что нужны обёртке.
  2. Обёртка должна вызвать callback — функция вызов() запускает выполнение всей цепочки шагов.
  3. Callback возвращает результат — обёртка может получить результат от вызов() и проверить, является ли он ошибкой, используя функцию это_ошибка(). Это полезно для обработки ошибок на уровне обёртки.
  4. Поддерживает асинхронность — обёртка может быть асинхронной функцией, и callback также может возвращать Promise.
  5. Обёртка может возвращать ошибку — если обёртка возвращает ошибку, то цепочка вернёт эту ошибку, если обёртка не возвращает ничего, то цепочка вернёт то, что возвращают шаги.

Асинхронное выполнение

Путь поддерживает как синхронные шаги, так и асинхронные. Если все шаги синхронные, то и результирующая функция будет синхронной. Если же хоть один из шагов будет асинхронным, то и результирующая функция будет асинхронной.

import { путь } from "@vr1/flow-r";

async function загрузи_пользователя(вход: { ключ_пользователя: string }): { пользователь: Пользователь } {
  // например, асинхронное получение id пользователя по имени
  return { пользователь: await /* код, работающий с БД, например */ };
}

function почтовый_адрес_пользователя(вход:  { пользователь: Пользователь }): { email: string } {
  // синхронный шаг, возвращает email по id
  return { email: `user${вход.ключ_пользователя}@mail.com` };
}

async function отправь_письмо(вход: { email: string, содержимое_письма: string }): Promise<void> {
  await /* код, работающий с email */
}

const отправь_письмо_пользователю =
  путь("отправь_письмо_пользователю")
    .шаг(загрузи_пользователя)
    .шаг(почтовый_адрес_пользователя)
    .шаг(отправь_письмо)
    .выход();

// автоматически будет иметь тип:
// (вход: { ключ_пользователя: string, содержимое_письма: string }) => Promise<void | Ошибка>;

async function пример() {
  await отправь_письмо_пользователю({ ключ_пользователя: "Пользователь123", содержимое_письма: "здравствуйте, бла-бла" });
}

Для подобных методов, как правило, не нужно, чтобы они что-либо возвращали. Поэтому, чтобы не засорять контекст вызывающей функции можно использовать пустой возврат.

const отправь_письмо_пользователю =
  путь("отправь_письмо_пользователю")
    .шаг(загрузи_пользователя)
    .шаг(почтовый_адрес_пользователя)
    .шаг(отправь_письмо)
    .выход();
// автоматически будет иметь тип:
// (вход: { ключ_пользователя: string, содержимое_письма: string }) => Promise<void | Ошибка>;

Обработка ошибок

Как и монада Either, flow-r работает с ошибками, избегая исключений. Любой из шагов цепочки может вернуть ошибку, и тогда выполнение всей цепочки прервётся и функция, представляющая собой цепочку сразу вернёт эту ошибку.

Параллельное выполнение - охват

Иногда нужно выполнить несколько независимых действий параллельно и объединить их результаты. Для этого используется функция и (или охват для именованного варианта).

import { и, путь } from "@vr1/flow-r";

function загрузи_профиль(вход: { id_пользователя: number }): { профиль: Профиль } {
  // загрузка профиля из БД
  return { профиль: /* ... */ };
}

function загрузи_настройки(вход: { id_пользователя: number }): { настройки: Настройки } {
  // загрузка настроек из БД
  return { настройки: /* ... */ };
}

function загрузи_уведомления(вход: { id_пользователя: number }): { уведомления: Уведомление[] } {
  // загрузка уведомлений из БД
  return { уведомления: /* ... */ };
}

// Все три функции выполнятся параллельно, результаты объединятся
const загрузи_данные_пользователя =
  и(загрузи_профиль)
    .и(загрузи_настройки)
    .и(загрузи_уведомления)
    .выход("профиль", "настройки", "уведомления");

// Тип результата автоматически выведется как:
// (вход: { id_пользователя: number }) => { профиль: Профиль, настройки: Настройки, уведомления: Уведомление[] } | Ошибка

Если любая из параллельных функций вернёт ошибку, весь охват вернёт ошибку с информацией обо всех ошибках.

Охват можно использовать как шаг в пути:

const показать_дашборд =
  путь("показать_дашборд")
    .шаг(и(загрузи_профиль).и(загрузи_настройки).и(загрузи_уведомления))
    .шаг(отрендери_дашборд);

Ограничение выхода работает аналогично пути:

const загрузи_только_профиль =
  и(загрузи_профиль)
    .и(загрузи_настройки)
    .выход("профиль");

// или через селектор
const загрузи_только_профиль_2 =
  и(загрузи_профиль)
    .и(загрузи_настройки)
    .выход(({ профиль }) => ({ профиль }));

Условное выполнение - если

Для выполнения шага только при выполнении условия используется функция если. Предикат должен возвращать объект { условие: boolean }.

import { если, путь } from "@vr1/flow-r";

function пользователь_премиум(вход: { пользователь: Пользователь }): { условие: boolean } {
  return { условие: вход.пользователь.тариф === "премиум" };
}

function загрузи_премиум_контент(вход: { пользователь: Пользователь }): { контент: Контент } {
  return { контент: /* премиум контент */ };
}

function загрузи_базовый_контент(вход: { пользователь: Пользователь }): { контент: Контент } {
  return { контент: /* базовый контент */ };
}

// Только то (без иначе) — если условие false, результат будет undefined
const загрузи_бонус =
  если(пользователь_премиум)
    .то(загрузи_премиум_контент);

// С веткой иначе — всегда выполнится один из вариантов
const загрузи_контент_по_тарифу =
  если(пользователь_премиум)
    .то(загрузи_премиум_контент)
    .иначе(загрузи_базовый_контент);

Использование в пути:

const показать_контент =
  путь("показать_контент")
    .шаг(загрузи_пользователя)
    .шаг(
      если(пользователь_премиум)
        .то(загрузи_премиум_контент)
        .иначе(загрузи_базовый_контент)
    )
    .шаг(отобрази_контент)
    .выход("какой то результат");

Прерывание по условию - ошибка_если

Функция ошибка_если позволяет прервать выполнение цепочки, если выполняется определённое условие. Полезно для валидации входных данных или проверки бизнес-правил.

import { ошибка_если, путь } from "@vr1/flow-r";

function пользователь_заблокирован(вход: { пользователь: Пользователь }): { условие: boolean } {
  return { условие: вход.пользователь.статус === "заблокирован" };
}

function баланс_недостаточен(вход: { счёт: Счёт, сумма: number }): { условие: boolean } {
  return { условие: вход.счёт.баланс < вход.сумма };
}

// Если условие true — цепочка прервётся с ошибкой
const выполни_перевод =
  путь("выполни_перевод")
    .шаг(загрузи_пользователя)
    .шаг(ошибка_если(пользователь_заблокирован, "Пользователь заблокирован"))
    .шаг(загрузи_счёт)
    .шаг(ошибка_если(баланс_недостаточен, "Недостаточно средств на счёте"))
    .шаг(проведи_операцию)
    .выход();

Если пользователь_заблокирован вернёт { условие: true }, то функция выполни_перевод сразу вернёт ошибку с текстом "Пользователь заблокирован", а шаги загрузи_счёт и проведи_операцию не будут выполнены.

Альтернативно, можно использовать более развёрнутый синтаксис:

.шаг(если(пользователь_заблокирован).то(верни_ошибку("Пользователь заблокирован")))

Ограничения

Зарезервированные поля

Некоторые имена полей зарезервированы библиотекой и не должны использоваться во входных и выходных объектах ваших функций:

| Поле | Причина | |------|---------| | __ошибка | Используется внутри библиотеки для маркировки объектов ошибок. Если ваш объект содержит это поле, он будет воспринят как ошибка и прервёт выполнение цепочки. | | условие | Зарезервировано для предикатов (если, ошибка_если). Предикаты обязаны возвращать объект вида { условие: boolean }. Использование этого поля в обычных шагах может привести к конфликтам. | | шаг | Зарезервировано как метод fluent API для добавления шагов в путь. | | обертка | Зарезервировано как метод fluent API для добавления обёртки к пути. | | и | Зарезервировано как метод fluent API для добавления параллельных действий в охват. | | выход | Зарезервировано как метод fluent API для ограничения выходных полей. |

// ❌ Плохо — поле __ошибка зарезервировано
function плохой_шаг(вход: { данные: string }): { __ошибка: string } {
  return { __ошибка: "что-то" }; // будет воспринято как ошибка!
}

// ❌ Плохо — поле условие зарезервировано для предикатов
function плохой_шаг2(вход: { x: number }): { условие: string } {
  return { условие: "готово" }; // конфликт с типом предиката
}

// ❌ Плохо — поля шаг, и, выход зарезервированы для fluent API
function плохой_шаг3(вход: { x: number }): { шаг: number, и: string, выход: boolean } {
  return { шаг: 1, и: "что-то", выход: true }; // конфликт с методами API
}

// ✅ Хорошо — используйте другие имена полей
function хороший_шаг(вход: { данные: string }): { результат: string } {
  return { результат: "готово" };
}

Лицензия

MIT