Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# FEATURES.md

## Framing

* `execute (series)` (Iteration):
- It works *horizontally*. It takes a list of items and applies one transformation to each item in parallel or sequence.
- Orchestrate the list and report progress."
- Process independent items.
- stateless
- default to collect error strategy

* `scan` (Iteration)
- Process dependent items
- stateful.
- locked on failFast error strategy

* `filter`
- Selects items horizontally.
- Do not care if items are dependent or not
- stateless
- default to collect error strategy

* `pipe / pipeAsync` (Composition):
- It works *vertically*.
- It takes one item and passes it through a chain of functions, one after the other.
- should *not* handle iterables.
- is just a "Function Builder." It creates a single, composite function f.
- has no error strategy.

* `tryCatch` is a function wrapper
- "Protect the work" of the function
- is effectively a pipeline of length 1.
- Wraps one function with "Middleware/Lifecycle" (Start, Success, Error, Finally).
- Can handle progress / error notification even without being run in a series
- Deep Telemetry: Wrap fn in pipeline to Log specifically when step 2 of a 5-step pipe fails.
- Can be extended with specific progress/error notifications use-cases

* `retry`: a specialized trycatch
- Perfect to combine in pipelines

## `series` Feature Set

* **Error Strategies**
* **Fail Fast:** Stops execution immediately upon the first error.
* **Collect:** Gathers all errors and continues processing until the end.

* **Termination Control (`take`)**
* Allows processing a subset of data (e.g., "process only the first N items").
* Essential for working with infinite generators or streams.

* **Universal Input**
* Works on Arrays, Streams, Generators, and any Async Iterable.

* **Universal Mapper**
* Handles both Synchronous and Asynchronous mapper functions automatically.

* **Functional Flexibility**
* **Immediate Mode:** `safeMap(data, fn)` runs instantly.
* **Curried Mode:** `safeMap(fn)(data)` creates a reusable executable, ideal for pipelines.

* **Order Guarantee**
* Because execution is sequential, output order strictly matches input order (no race conditions).

* **Structured Results**
* Always returns a predictable object: `{ results, errors, failure }`.
* Errors are treated as data, removing the need for consumer-side `try/catch` blocks.
149 changes: 13 additions & 136 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,146 +1,23 @@
# Pipelean

Async-first, error-aware data transformation library. Never write a for loop again.
A pragmatic library for sequential async operations with robust error handling.

---
Why?

## Core Concepts
Standard Promise.all crashes on the first error. Promise.allSettled gives you a messy array of statuses. Pipelean gives you structured results, error strategies, and flow control out of the box.

### Error Strategies
## The Concept

Three strategies control how errors are handled across all operations: `failFast` (default, stops immediately), `skip` (continues, collects errors), `collect` (continues, yields error objects). Every operation accepts `{onError: strategy}` in its options.
pipe: Compose functions vertically.
series: Execute them horizontally over a list.

### Operations vs Pipelines
## Quick Example

* **Operations** (`safeMap`, `safeFilter`) transform individual items; they accept immediate or curried arguments.
* **Pipelines** (`safePipe`, `safeAsyncIterator`) compose operations and manage error propagation across steps.
import { pipe, series, retry } from 'pipelean';// 1. Build your workflowconst pipeline = pipe( processData, retry(saveToDb, { attempts: 3 }), // Built-in resiliency notifyUI);// 2. Execute safelyconst { results, errors } = await series(items, pipeline);// results: Successful items// errors: [{ item, error }, ...] -> Structured failures

### Map, Filter, Scan, Batch — What's the Difference?

## Documentation

* `safeMap` transforms each item (sync or async)
* `safeFilter` keeps items matching a predicate
* `scanSeries` accumulates a value while iterating
* `mapSeries` maps with a concurrency limit.

The all return `{results, errors, failure}` for error handling.

### Error Strategies Are Consistent

Every *operation* and every *pipeline* defaults to `failFast`: stop on first error. Use `{onError: skip}` to continue past errors, or `{onError: collect}` to gather them. Same semantics everywhere — no surprises.

---

## Operations

### safeMap

```js
// Immediate: array input, transform, options
const {results, errors, failure} = await safeMap(data, x => x * 2, {onError: skip})

// Curried: for use in pipelines
const double = safeMap(x => x * 2)
const pipeline = safePipe(double, ...)
```

Async transforms supported. Default: `failFast`. Returns `{results, errors, failure}`.

### safeFilter

```js
// Immediate
const {results, errors, failure} = await safeFilter(data, x => x > 5, {onError: collect})

// Curried
const bigOnly = safeFilter(x => x > 5)
```

Predicate can be async. Default: `failFast`. Same return structure.

### mapSeries

Shortcut for `safeMap({onError: none})`


### scanSeries

```js
const runningTotal = await scanSeries(data, (acc, item) => acc + item, 0)
// Returns: [1, 3, 6, 10, ...]
```

Accumulates a value while iterating. Returns all intermediate results.

---

## Pipelines

### safePipe

```js
const {results, errors, failure} = await safePipe(
safeMap(x => x * 2),
safeFilter(x => x > 5),
safeMap(async x => enrichData(x))
)(data)
```

Chains operations with error propagation. Each step's errors accumulate; `failFast` in any step stops the entire pipeline. Returns structured result.

### safeAsyncIterator

```js
async function* enrichStream(source) {
const iterator = safeAsyncIterator(source, async item => ({...item, meta: await fetch(item.id)}), {onError: collect})
for await (const result of iterator) {
yield result
}
}
```

Generator-based iteration. Lazy evaluation stops when you stop consuming. Default: `failFast`. Perfect for streaming large datasets.

---

## Example: Full Pipeline

```js
const data = [1, 2, 3, 4, 5]

const {results, errors, failure} = await safePipe(
safeMap(x => x * 2, {onError: skip}), // double
safeFilter(x => x > 4, {onError: skip}), // keep > 4
safeMap(async x => ({value: x, enriched: await fetchMeta(x)}), {onError: collect})
)(data)

// results: transformed data
// errors: accumulated errors from all steps
// failure: first failFast error (null if no failFast errors)
```

---

## tryCatch

Wraps a function with hooks for start, success, error, finally. Not part of error strategies — use for side effects (logging, cleanup). For error handling in pipelines, use operations' `onError` option.

```js
const wrapped = tryCatch(asyncFn, {
onStart: () => console.log('starting'),
onSuccess: (result) => console.log('done', result),
onError: (error) => console.error(error),
onFinally: () => cleanup()
})

const result = await wrapped(args)
```

---

## When to Use What

**Need simple data transformation?** Start with `safeMap` + `safeFilter` in a `safePipe`.

**Processing streams or large datasets?** Use `safeAsyncIterator` for lazy, memory-efficient iteration.

**Mapping with concurrency limits?** Use `mapSeries`.
* [Architecture](docs/architecture.md) : The philosophy and design principles.
* [Guide](docs/guide.md) : Core concepts and usage patterns.
<!-- * [API Reference](docs/api.md) : Detailed feature sets. -->
15 changes: 15 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Design Principles & Architecture

The Philosophy: Pragmatism Over Purity

We built this library to be a practical tool for executing tasks, not a theoretical academic exercise.

Many functional libraries are "Iterator-First" (lazy, yielding generators). We explicitly rejected that approach. Why?

* Debugging is harder: Lazy execution makes stack traces difficult to read.
* Control is deferred: You don't know if an operation fails until you consume the iterator.
* Complexity: It requires users to understand generators and composition patterns just to run a simple list of tasks.

Our Approach: Eager Execution.

We prefer Explicit Results over Lazy Iterables. When you run series or scan, the work happens immediately. You get a structured report { results, errors, failure } back. No surprises.
6 changes: 0 additions & 6 deletions docs/docs.md

This file was deleted.

41 changes: 41 additions & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Guide

## Core

We have four distinct tools, separated by the Direction of Data Flow and the State Dependency.

1. series (Horizontal / Stateless)
2. scan (Horizontal / Stateful)
3. filter (Horizontal / Selection)
4. pipe (Vertical / Composition)

## The Function Wrappers

These are "Middleware" for your functions. They wrap a single unit of work to add behavior.

* tryCatch (Lifecycle Middleware)
* retry (Resiliency Middleware)

## Composition in Action

The power of this library comes from combining these primitives.

Example: A robust download pipeline

```js
// 1. Define the "Work"
// pipe: Chains the logic vertically.
const pipeline = pipe(
retry(downloadTrack, 3), // Resiliency: Retry 3 times
processTrack, // Pure logic
retry(updateDb, 3), // Resiliency: Retry DB 2 times
notifyUI // Side effect
)

// 2. Execute the "Work"
// series: Runs the pipeline horizontally over the list.
const { results, errors } = await series(tracks, pipeline, {
strategy: 'collect', // Don't stop if one track fails
onProgress: updateBar // Report global progress
})
```
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "lib-template",
"version": "0.1.0",
"description": "JavaScript library template.",
"name": "pipelean",
"version": "0.2.0",
"description": "A pragmatic library for sequential async operations with first-class error handling.",
"type": "module",
"license": "MIT",
"homepage": "https://github.com/ildella/lib-template",
"homepage": "https://github.com/ildella/pipelean",
"repository": {
"type": "git",
"url": "git://github.com/ildella/lib-template.git"
"url": "git://github.com/ildella/pipelean.git"
},
"author": {
"name": "Daniele Dellafiore",
Expand Down
Loading