strop
v0.1.3
Published
A modern TypeScript stream operations library
Maintainers
Readme
Strop
A modern, type-safe TypeScript stream library inspired by Highland.js, featuring lazy evaluation, error handling via Result types, and powerful stream composition.
Features
- Type-safe: Full TypeScript support with strict typing
- Result-based error handling: No exceptions, explicit error handling with Result types
- Lazy evaluation: Streams only compute values when consumed
- Composable: Rich set of operators for transforming and combining streams
- Memory efficient: Handles large datasets without loading everything into memory
- Promise & async/await ready: Works seamlessly with modern async patterns
- Zero dependencies: Lightweight and self-contained
Installation
npm install strop
# or
yarn add strop
# or
pnpm add stropQuick Start
import { Stream } from 'strop';
// Create a stream from an array
const numbers = Stream.fromArray([1, 2, 3, 4, 5]);
// Transform and consume
const result = await numbers
.map(x => x * 2)
.filter(x => x > 5)
.toArray();
if (result.ok) {
console.log(result.value); // [6, 8, 10]
}Core Concepts
Streams
Streams are lazy, composable sequences of values that can be transformed and consumed. They support:
- Transformation:
map,filter,flatMap,scan - Selection:
take,drop,where,compact - Grouping:
batchvalues into groups,groupby property or function - Rate Limiting:
throttleto control flow rate - Combination:
concat,merge,flatten - Consumption:
toArray,toPromise,each,reduce
Result Types
Instead of throwing exceptions, all stream operations return Result<T, E> types:
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };This makes error handling explicit and type-safe.
Examples
Map and Filter
import { Stream } from 'strop';
const result = await Stream.fromArray([1, 2, 3, 4, 5])
.map(x => x * 2)
.filter(x => x > 5)
.toArray();
// { ok: true, value: [6, 8, 10] }Error Handling
import { Stream } from 'strop';
const result = await Stream.fromArray([1, 2, 3])
.map(x => {
if (x === 2) throw new Error('Invalid value');
return x * 2;
})
.errors((error, push) => {
console.error('Caught error:', error);
push({ type: 'value', value: 0 }); // Recover with default
})
.toArray();
// { ok: true, value: [2, 0, 6] }Async Operations
import { Stream } from 'strop';
const fetchUsers = async (id: number) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
await Stream.fromArray([1, 2, 3])
.flatMap(id => Stream.fromPromise(fetchUsers(id)))
.each(user => console.log(user));Batching
import { Stream } from 'strop';
const result = await Stream.fromArray([1, 2, 3, 4, 5, 6, 7])
.batch(3)
.toArray();
// { ok: true, value: [[1, 2, 3], [4, 5, 6], [7]] }Grouping
import { Stream } from 'strop';
// Group by property
const data = [
{ type: 'fruit', name: 'apple' },
{ type: 'veggie', name: 'carrot' },
{ type: 'fruit', name: 'banana' }
];
const result = await Stream.fromArray(data).group('type').toArray();
// { ok: true, value: [{
// fruit: [{ type: 'fruit', name: 'apple' }, { type: 'fruit', name: 'banana' }],
// veggie: [{ type: 'veggie', name: 'carrot' }]
// }]}
// Group by function
const numbers = [1, 2, 3, 4, 5, 6];
const grouped = await Stream.fromArray(numbers)
.group(n => n % 2 === 0 ? 'even' : 'odd')
.toArray();
// { ok: true, value: [{ odd: [1, 3, 5], even: [2, 4, 6] }]}Pipeline Composition
import { Stream, pipeline } from 'strop';
const transform = pipeline<number, number>(
s => s.map(x => x * 2),
s => s.filter(x => x > 5),
s => s.take(3)
);
const result = await transform(Stream.fromArray([1, 2, 3, 4, 5])).toArray();
// { ok: true, value: [6, 8, 10] }Rate Limiting
import { Stream } from 'strop';
// Allow max 2 values per 100ms window
const throttled = Stream.fromArray([1, 2, 3, 4, 5])
.throttle(2, 100);
// First 2 values pass immediately, rest are delayed
// Values are not dropped - backpressure builds up
const result = await throttled.toArray();
// { ok: true, value: [1, 2, 3, 4, 5] }
// Takes ~200ms to complete as values 3-5 are delayedNode.js Callback Wrapping
import { wrapCallback } from 'strop';
import fs from 'fs';
const readFile = wrapCallback(fs.readFile);
const content = await readFile('package.json', 'utf8').toPromise();
if (content.ok) {
console.log(content.value);
}Documentation
API Overview
Static Constructors
Stream.fromArray(array)- Create stream from arrayStream.fromPromise(promise)- Create stream from promiseStream.fromAsyncIterable(iterable)- Create stream from async iterableStream.fromGenerator(generator)- Create stream from generator functionStream.fromNodeStream(stream)- Create stream from Node.js readable streamStream.of(value)- Create stream with single valueStream.empty()- Create empty streamStream.fromError(error)- Create stream that emits an error
Transformations
.map(fn)- Transform each value.filter(fn)- Keep values matching predicate.flatMap(fn)- Map and flatten nested streams.take(n)- Take first n values.drop(n)- Skip first n values.batch(n)- Group values into batches.scan(initial, fn)- Accumulate with intermediate values.pluck(key)- Extract property from objects.where(props)- Filter by property values.compact()- Remove falsy values.group(keyOrFn)- Group values by property or function.throttle(maxValues, timeWindowMs)- Limit rate of values
Combination
.concat(other)- Append another stream.flatten()- Flatten nested streams.merge()- Merge multiple streams.series()- Process streams sequentially.parallel(n)- Process streams with concurrency limit
Consumption
.toArray()- Collect all values into array.toPromise()- Get single value as promise.each(fn)- Iterate over each value.reduce(initial, fn)- Reduce to single value
Error Handling
.errors(handler)- Handle and recover from errors
Utilities
.through(transform)- Pipe through transform function
Development
# Install dependencies
yarn install
# Run tests
yarn test
# Build
yarn build
# Type check
yarn typecheckLicense
MIT
Contributing
Contributions are welcome! Please read our contributing guidelines and submit PRs.
