git clone https://github.com/sakobu/railway-ts-pipelines.git
cd railway-ts-pipelines
bun installPrerequisites: Bun, TypeScript knowledge, functional programming basics.
bun test # Run tests
bun test --watch # Watch mode
bun test --coverage # With coverage
bun run typecheck # Type check
bun run lint # Lint
bun run lint:fix # Auto-fix
bun run format # Format code
bun run format:check # Check formatting
bun run build # Build library
bun run check # typecheck + lint + testsrc/
├── option/ # Option<T>
├── result/ # Result<T, E>
├── schema/ # Validators
├── composition/ # pipe, flow, curry
└── index.ts # Root exports
tests/ # Mirrors src/
examples/ # Working examples
docs/ # Documentation
anytypes forbidden - useunknownfor untrusted input- Pure functions only - no side effects except in examples
- Data-last parameters for composition
- Explicit returns (except arrow functions)
- Use
readonlyfor object properties - Prefer
constoverlet - Type guards with predicates:
(x): x is Type - All public APIs need JSDoc with examples
Must use this: void for referential transparency:
export function myFunction<A, B>(this: void, input: A, transform: (this: void, a: A) => B): B {
return transform(input);
}/**
* Brief description of what the function does.
*
* @example
* const result = map(some(5), (x) => x * 2);
* // some(10)
*
* @param option - Description
* @param fn - Description
* @returns Description
*/
export function map<T, U>(option: Option<T>, fn: (value: T) => U): Option<U> {
return option.some ? some(fn(option.value)) : none();
}Tests mirror src/ structure. Test both success and error paths.
import { describe, test, expect } from 'bun:test';
describe('Option.map', () => {
test('transforms Some values', () => {
const result = map(some(5), (x) => x * 2);
expect(result).toEqual(some(10));
});
test('preserves None', () => {
const result = map(none<number>(), (x) => x * 2);
expect(result).toEqual(none());
});
test('does not call function on None', () => {
let called = false;
map(none<number>(), () => {
called = true;
return 0;
});
expect(called).toBe(false);
});
});- Open issue first for new features
- One feature/fix per PR
- Add tests for all new code
- Update docs if behavior changes
- Run
bun run checkbefore submitting - Clear PR description with issue references
When adding schema validators:
- Add to appropriate file in
src/schema/ - Export from
src/schema/index.ts - Add JSDoc with examples
- Add tests in
tests/schema/ - Add example in
examples/schema/
export function myValidator(config: Config): Validator<Input, Output> {
return (value, path = []) => {
if (!isValid(value)) {
return err([{ path, message: 'error message' }]);
}
return ok(transform(value));
};
}Uses tsup with multiple entry points:
src/index.ts→dist/index.{mjs,cjs}src/option/index.ts→dist/option/index.{mjs,cjs}src/result/index.ts→dist/result/index.{mjs,cjs}src/composition/index.ts→dist/composition/index.{mjs,cjs}src/schema/index.ts→dist/schema/index.{mjs,cjs}
Each generates:
- ESM (
.mjs) - CJS (
.cjs) - TypeScript declarations (
.d.ts)
Use conventional commits:
type(scope): brief description
Longer description if needed
Fixes #123
Types: feat, fix, docs, test, refactor, perf, chore
Examples:
feat(result): add mapErr function
fix(schema): correct type inference for nested optional fields
docs(readme): update installation instructions
test(option): add tests for combine() with mixed Some/None
- Bug fixes with test cases
- New validators (email, url, date ranges)
- Performance improvements with benchmarks
- Documentation improvements
- Example pipelines
- New core abstractions (open issue first)
- Breaking changes (probably not pre-1.0)
- API bloat
- Anything requiring
anytypes
- Bug reports: Open issue
- Feature ideas: Open issue (discuss first)
- Questions: GitHub Discussions
By contributing, you agree your contributions will be licensed under MIT.