### Bash: Running Railway-TS Pipelines Examples Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/GETTING_STARTED.md Instructions on how to clone the Railway-TS pipelines repository and run the example launch decision pipeline. ```bash git clone https://github.com/sakobu/railway-ts-pipelines.git cd railway-ts-pipelines bun install # Run all examples bun run examples/index.ts # Or run specific examples bun run examples/complete-pipelines/async-launch.ts ``` -------------------------------- ### Install Railway TS Pipelines with Bun Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/GETTING_STARTED.md This command installs the Railway TS Pipelines library using the Bun package manager. It is compatible with npm, pnpm, and yarn as well. Ensure you have Node.js 18+ and TypeScript 5.0+ installed. ```bash bun add @railway-ts/pipelines # or npm, pnpm, yarn ``` -------------------------------- ### Project Setup with Bash Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/README.md Commands to clone the repository and run examples using Bun runtime. Shows the directory structure of included examples. ```bash git clone https://github.com/sakobu/railway-ts-pipelines.git cd railway-ts-pipelines bun install bun run examples/index.ts ``` -------------------------------- ### Clone and install dependencies using Bun Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/CONTRIBUTING.md Initial project setup commands for cloning the repository and installing dependencies using Bun package manager. Requires Git and Bun to be pre-installed on the system. Executes sequential commands to prepare the development environment. ```bash git clone https://github.com/sakobu/railway-ts-pipelines.git cd railway-ts-pipelines bun install ``` -------------------------------- ### Import Specific Functions from Subpaths Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/GETTING_STARTED.md Demonstrates importing specific functions from subpaths within the '@railway-ts/pipelines' library. This approach is recommended for better tree-shaking. It shows imports for 'option', 'result', 'composition', and 'schema' modules. ```typescript import { some, none, map } from '@railway-ts/pipelines/option'; import { ok, err, flatMap } from '@railway-ts/pipelines/result'; import { pipe, flow } from '@railway-ts/pipelines/composition'; import { validate, object, required } from '@railway-ts/pipelines/schema'; ``` -------------------------------- ### Import Functions from Root Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/GETTING_STARTED.md Shows how to import functions directly from the root of the '@railway-ts/pipelines' library. When importing from root, shared functions receive type suffixes (e.g., 'mapOption', 'mapResult') to distinguish their usage. ```typescript import { mapOption, mapResult, pipe, ok } from '@railway-ts/pipelines'; ``` -------------------------------- ### Document functions with JSDoc in TypeScript Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/CONTRIBUTING.md Standard JSDoc template for documenting public APIs with brief descriptions, usage examples, parameter details, and return value information. Includes a concrete implementation example of an Option.map function. All public functions must follow this documentation format. ```typescript /** * 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(option: Option, fn: (value: T) => U): Option { return option.some ? some(fn(option.value)) : none(); } ``` -------------------------------- ### Run development commands with Bun Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/CONTRIBUTING.md Comprehensive list of Bun commands for testing, type checking, linting, formatting, and building the TypeScript library. Includes watch mode for continuous testing and a consolidated check command. All commands assume Bun is installed and the project dependencies are available. ```bash 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 + test ``` -------------------------------- ### TypeScript: Launch Decision Pipeline Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/GETTING_STARTED.md Implements a launch decision pipeline using Railway-TS. It validates launch parameters, fetches weather data, and determines whether the launch should proceed based on wind conditions. This showcases validation, asynchronous operations, and functional composition. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { err, fromPromise, match, ok, andThen, type Result } from '@railway-ts/pipelines/result'; import { formatErrors, object, required, chain, parseNumber, min, max, stringEnum, parseDate, validate, type ValidationError, type ValidationResult, type InferSchemaType, } from '@railway-ts/pipelines/schema'; // Schema const launchSchema = object({ vehicleType: required(stringEnum(['falcon9', 'atlas5'] as const)), payload: required(chain(parseNumber(), min(1000), max(25_000))), latitude: required(chain(parseNumber(), min(-90), max(90))), longitude: required(chain(parseNumber(), min(-180), max(180))), windowStart: required(parseDate()), }); type LaunchParams = InferSchemaType; // Weather API types type WeatherData = { wind_speed_10m: number; wind_direction_10m: number; wind_gusts_10m: number; }; // Helper for API responses const toJsonIfOk = (res: Response) => (res.ok ? res.json() : Promise.reject(`HTTP ${res.status}`)); // Fetch weather and combine with params const fetchWeatherWithParams = async (params: LaunchParams): Promise> => { const url = new URL('https://api.open-meteo.com/v1/forecast'); url.searchParams.append('latitude', params.latitude.toString()); url.searchParams.append('longitude', params.longitude.toString()); url.searchParams.append('current', 'wind_speed_10m,wind_direction_10m,wind_gusts_10m'); url.searchParams.append('wind_speed_unit', 'ms'); const result = await fromPromise(fetch(url.toString()).then(toJsonIfOk)); return match(result, { ok: (data) => ok({...params, weather: data.current}), err: (msg) => err([{ path: ['weather_api'], message: String(msg) }]), }); }; // Assess launch conditions const assessLaunchConditions = async (context: LaunchParams & WeatherData): Promise> => { const windLimits: Record = { falcon9: 15, atlas5: 12, }; const maxWind = windLimits[context.params.vehicleType]; const actualMaxWind = Math.max(context.weather.wind_speed_10m, context.weather.wind_gusts_10m); const isGo = actualMaxWind <= maxWind; return ok(isGo ? 'GO' : 'NO GO'); }; // Main pipeline const evaluateLaunch = async (input: unknown): Promise> => { const validationResult = validate(input, launchSchema); const result = await pipe( validationResult, (r) => andThen(r, fetchWeatherWithParams), (r) => andThen(r, assessLaunchConditions), ); return match>(result, { ok: (decision) => ({ valid: true, data: decision }), err: (errors) => ({ valid: false, errors: formatErrors(errors) }), }); }; // Usage const result = await evaluateLaunch({ vehicleType: 'falcon9', payload: 1000, latitude: 28.5721, longitude: -80.648, windowStart: new Date('2025-01-01'), }); console.log(result); ``` -------------------------------- ### Build a Data Validation and Transformation Pipeline Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/GETTING_STARTED.md This TypeScript code snippet demonstrates building a pipeline using Railway TS. It defines a schema for validation, uses `pipe` to chain validation and a computation, and `match` to handle the final result, automatically propagating errors. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { ok, match, andThen } from '@railway-ts/pipelines/result'; import { validate, object, required, chain, parseNumber, min, formatErrors, type ValidationError, type ValidationResult, } from '@railway-ts/pipelines/schema'; // 1. Define schema const schema = object({ x: required(chain(parseNumber(), min(0))), y: required(chain(parseNumber(), min(1))), }); // 2. Build pipeline async function compute(input: unknown): Promise> { const result = await pipe(validate(input, schema), (r) => andThen(r, ({ x, y }) => ok(x / y))); return match>(result, { ok: (value) => ({ valid: true, data: value }), err: (errors) => ({ valid: false, errors: formatErrors(errors) }), }); } // 3. Use it await compute({ x: '10', y: '2' }).then(console.log); // { valid: true, data: 5 } await compute({ x: '-5', y: '0' }).then(console.log); // { valid: false, errors: [...] } ``` -------------------------------- ### Write tests using Bun test runner Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/CONTRIBUTING.md Example test suite structure using Bun's built-in test runner for validating Option type functionality. Shows multiple test cases covering success paths, error paths, and side effect verification. Tests should mirror the source directory structure and include both positive and negative scenarios. ```typescript 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(), (x) => x * 2); expect(result).toEqual(none()); }); test('does not call function on None', () => { let called = false; map(none(), () => { called = true; return 0; }); expect(called).toBe(false); }); }); ``` -------------------------------- ### Quick Start: Validate and Compute with Result Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/README.md Demonstrates validating input data against a schema, performing a computation, and handling the Result type. It uses 'pipe' for composition and 'match' for branching based on success or error. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { ok, match, andThen } from '@railway-ts/pipelines/result'; import { validate, object, required, chain, parseNumber, min, formatErrors, type ValidationError, type ValidationResult, } from '@railway-ts/pipelines/schema'; const schema = object({ x: required(chain(parseNumber(), min(0))), y: required(chain(parseNumber(), min(1))), }); async function compute(input: unknown): Promise> { const result = await pipe(validate(input, schema), (r) => andThen(r, ({ x, y }) => ok(x / y))); return match>(result, { ok: (value) => ({ valid: true, data: value }), err: (errors) => ({ valid: false, errors: formatErrors(errors) }), }); } await compute({ x: 10, y: 2 }).then(console.log); // { valid: true, data: 5 } ``` -------------------------------- ### Example Usage of Point-Free Composition (TypeScript) Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/RECIPES.md Shows a practical example of using point-free composition with the flow function and helper functions like mapWith and flatMapWith. This demonstrates how to chain multiple operations, including validation and data transformation, in a concise and readable manner. ```typescript import { flow } from '@railway-ts/pipelines/composition'; import { validate } from '@railway-ts/pipelines/schema'; const processData = flow( (input: unknown) => validate(input, schema), mapWith(transformData), flatMapWith(validateBusinessRules), tapWith((data) => console.log('Processing:', data)), mapWith(enrichData), ); ``` -------------------------------- ### Flow for Reusable Pipelines in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt This TypeScript example illustrates creating reusable pipeline functions using the flow combinator for validation and transformation. It depends on '@railway-ts/pipelines/composition' and '@railway-ts/pipelines/result', accepting inputs and applying a series of operations with error handling. Flow supports multiple arguments via composition, enabling reusable logic, though it requires defining the initial validation step. ```typescript import { flow } from '@railway-ts/pipelines/composition'; import { ok, map, flatMap, match } from '@railway-ts/pipelines/result'; // Build reusable pipeline const validateAndDouble = flow( (input: unknown) => typeof input === 'number' ? ok(input) : err('Not a number'), (r) => map(r, (n) => n * 2), (r) => flatMap(r, (n) => n < 100 ? ok(n) : err('Too large')) ); const result1 = validateAndDouble(21); // ok(42) const result2 = validateAndDouble('21'); // err('Not a number') const result3 = validateAndDouble(60); // err('Too large') // Compose with multiple input arguments const formatName = flow( (first: string, last: string) => `${first} ${last}`, (name) => name.toUpperCase(), (name) => name.trim() ); const fullName = formatName(' john ', ' doe '); // "JOHN DOE" ``` -------------------------------- ### Parser validators for data conversion Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Provides examples of various parser validators for converting string inputs to other types, including numbers, integers, booleans, dates, URLs, enums, and JSON. Uses functions like `parseNumber`, `parseDate`, `parseEnum`, and `parseJSON`. ```typescript import { parseNumber, parseInt, parseDate, parseISODate, parseURL, parseBool, parseJSON, parseEnum } from '@railway-ts/pipelines/schema'; // Parse various types from strings const numberResult = parseNumber()('42.5'); // ok(42.5) const intResult = parseInt()('42.5'); // ok(42) const boolResult = parseBool()('yes'); // ok(true) const dateResult = parseDate()('2024-01-15'); // ok(Date) const urlResult = parseURL()('https://example.com'); // ok(URL) // Parse enums with case-insensitive matching enum Status { Pending = 'PENDING', Approved = 'APPROVED' } const enumValidator = parseEnum(Status); const result = enumValidator('pending'); // ok('PENDING') // Parse JSON strings const jsonResult = parseJSON()('{"name":"John"}'); // ok({ name: 'John' }) ``` -------------------------------- ### Define composition functions in TypeScript Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/CONTRIBUTING.md Demonstrates the required function signature pattern for composition functions to ensure referential transparency. Uses the this: void parameter to prevent method context binding and enforce pure function behavior. This pattern is mandatory for all composition functions in the codebase. ```typescript export function myFunction(this: void, input: A, transform: (this: void, a: A) => B): B { return transform(input); } ``` -------------------------------- ### Implement schema validators in TypeScript Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/CONTRIBUTING.md Template for implementing custom schema validators that return Result types with detailed error paths. Accepts a configuration object and returns a validation function. The validator checks input validity and returns either a success result with transformed value or an error result with path-based error messages. ```typescript export function myValidator(config: Config): Validator { return (value, path = []) => { if (!isValid(value)) { return err([{ path, message: 'error message' }]); } return ok(transform(value)); }; } ``` -------------------------------- ### Pattern matching and error handling with Result Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Illustrates pattern matching on Result types using `match`. Shows combining multiple Results with `combine` (fail-fast) and `combineAll` (accumulate errors). Useful for robust error handling. ```typescript import { match, combine, combineAll } from '@railway-ts/pipelines/result'; // Match on both success and error const message = match(ok(42), { ok: (value) => `Success: ${value}`, err: (error) => `Error: ${error}` }); // "Success: 42" // Combine multiple Results - fails fast const allOrNothing = combine([ok(1), ok(2), ok(3)]); // ok([1, 2, 3]) const firstError = combine([ok(1), err('e1'), err('e2')]); // err('e1') - returns first error // combineAll collects all errors const allErrors = combineAll([ok(1), err('e1'), ok(3), err('e2')]); // err(['e1', 'e2']) - accumulates all errors ``` -------------------------------- ### Function Composition in TypeScript Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/README.md Demonstrates pipeline creation using pipe and flow utilities. Pipe executes immediately while flow creates reusable pipelines. Supports currying for functional patterns. ```typescript import { pipe, flow, curry } from '@railway-ts/pipelines/composition'; // Immediate execution const result = pipe( 5, (x) => x * 2, (x) => x + 1, ); // 11 // Build reusable pipeline const process = flow( (x: number) => x * 2, (x) => x + 1, ); process(5); // 11 ``` -------------------------------- ### Subpath Imports in TypeScript Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/README.md Recommended import pattern for tree-shaking. Shows how to import specific utilities from dedicated module paths to optimize bundle size. ```typescript import { some, none, map } from '@railway-ts/pipelines/option'; import { ok, err, flatMap } from '@railway-ts/pipelines/result'; import { pipe, flow } from '@railway-ts/pipelines/composition'; import { string, number, validate } from '@railway-ts/pipelines/schema'; ``` -------------------------------- ### Creating and Checking Result Values in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Shows how to create 'ok' (success) and 'err' (failure) Result values, and use type guards 'isOk' and 'isErr' for checking the Result status. Includes 'fromTry' and 'fromTryWithError' for converting try-catch blocks into Results. ```typescript import { ok, err, isOk, isErr, type Result } from '@railway-ts/pipelines/result'; // Create successful result const success: Result = ok(42); // Create error result const failure: Result = err('Invalid input'); // Type guards if (isOk(success)) { console.log(success.value); // 42 } if (isErr(failure)) { console.error(failure.error); // "Invalid input" } // Convert try-catch to Result import { fromTry, fromTryWithError } from '@railway-ts/pipelines/result'; const parsed = fromTry(() => JSON.parse('{"name":"John"}')); // ok({ name: "John" }) const withError = fromTryWithError(() => JSON.parse('invalid')); // err(Error) - preserves full Error object with stack trace ``` -------------------------------- ### Creating and Checking Option Values in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Demonstrates how to create 'some' and 'none' Option values and use type guards like 'isSome' and 'isNone' for safe value checking. It also shows converting nullable values to Option using 'fromNullable'. ```typescript import { some, none, isSome, isNone, type Option } from '@railway-ts/pipelines/option'; // Create Some containing a value const userAge: Option = some(25); // Create None for missing values const missingEmail: Option = none(); // Type guards if (isSome(userAge)) { console.log(userAge.value); // TypeScript knows value exists: 25 } if (isNone(missingEmail)) { console.log('No email provided'); // TypeScript knows no value exists } // Convert nullable values to Option import { fromNullable } from '@railway-ts/pipelines/option'; const maybeValue: Option = fromNullable(null); // none() const hasValue: Option = fromNullable(42); // some(42) ``` -------------------------------- ### Transforming Option Values with map, flatMap, and filter in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Illustrates transforming Option values using 'map' for simple transformations, 'flatMap' for chaining operations that return Options, and 'filter' to keep values satisfying a predicate. Uses the 'pipe' utility for composition. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { some, none, map, flatMap, filter } from '@railway-ts/pipelines/option'; // Map transforms the contained value const doubled = pipe( some(10), (o) => map(o, (n) => n * 2) ); // some(20) // flatMap chains operations that return Options const parseAndDouble = (s: string) => { const n = parseInt(s); return isNaN(n) ? none() : some(n * 2); }; const result = pipe( some('42'), (o) => flatMap(o, parseAndDouble) ); // some(84) // Filter keeps values matching a predicate const evenOnly = pipe( some(21), (o) => filter(o, (n) => n % 2 === 0) ); // none() ``` -------------------------------- ### Currying Functions in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt This TypeScript snippet demonstrates currying and uncurrying functions for partial application, allowing multi-argument functions to be called incrementally. It imports curry and uncurry from '@railway-ts/pipelines/composition', enabling flexible function invocation. The curry function transforms to curried form, and uncurry reverses it, with inputs/outputs clearly mapped, though limited to functions without optional parameters. ```typescript import { curry, uncurry } from '@railway-ts/pipelines/composition'; // Convert multi-argument function to curried form const add = (a: number, b: number, c: number) => a + b + c; const curriedAdd = curry(add); const add5 = curriedAdd(5); const add5and3 = add5(3); const result = add5and3(2); // 10 // Or call with all arguments at once const direct = curriedAdd(1)(2)(3); // 6 // Uncurry to restore original form const normalAdd = uncurry(curriedAdd); normalAdd(1, 2, 3); // 6 ``` -------------------------------- ### Chain asynchronous operations with Result Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Demonstrates chaining asynchronous operations that return Result types using `andThen` and `pipe`. Also shows converting Promises to Results with `fromPromise`. Handles success and error states. ```typescript import { andThen, fromPromise, ok, err } from '@railway-ts/pipelines/result'; import { pipe } from '@railway-ts/pipelines/composition'; // andThen chains async operations that return Results async function fetchUser(id: number) { return ok({ id, name: 'Alice', roleId: 1 }); } async function fetchRole(user: { roleId: number }) { return ok({ id: user.roleId, name: 'admin' }); } const result = await pipe( ok(123), (r) => andThen(r, fetchUser), (r) => andThen(r, fetchRole) ); // ok({ id: 1, name: 'admin' }) // Convert promises to Results const apiResult = await fromPromise( fetch('https://api.example.com/data') .then(res => res.json()) ); // ok(data) or err(error message) ``` -------------------------------- ### Option Type: Handling Nullable Values Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/README.md Illustrates the use of the Option type to manage potentially null or undefined values gracefully. It demonstrates mapping over an Option and matching to extract the value or provide a default. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { some, map, match } from '@railway-ts/pipelines/option'; const user = some({ name: 'Alice', age: 25 }); const name = pipe(user, (o) => map(o, (u) => u.name)); match(name, { some: (n) => console.log(n), none: () => console.log('No user'), }); // Output: Alice ``` -------------------------------- ### Async Validation and API Integration with Railway TS Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Validates input data against a schema, fetches external weather data via an API, and assesses launch conditions. It uses the Result type for error handling and `pipe` for composing asynchronous operations. Dependencies include `@railway-ts/pipelines`. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { ok, err, fromPromise, match, andThen } from '@railway-ts/pipelines/result'; import { validate, object, required, chain, parseNumber, min, max, stringEnum, parseDate, formatErrors, type ValidationError, type ValidationResult } from '@railway-ts/pipelines/schema'; // Define input schema const launchSchema = object({ vehicleType: required(stringEnum(['falcon9', 'atlas5'] as const)), payload: required(chain(parseNumber(), min(1000), max(25_000))), latitude: required(chain(parseNumber(), min(-90), max(90))), longitude: required(chain(parseNumber(), min(-180), max(180))), windowStart: required(parseDate()) }); type LaunchParams = InferSchemaType; // Fetch weather data async function fetchWeather(params: LaunchParams) { const url = `https://api.open-meteo.com/v1/forecast?latitude=${params.latitude}&longitude=${params.longitude}¤t=wind_speed_10m,wind_gusts_10m`; const result = await fromPromise( fetch(url).then(r => r.ok ? r.json() : Promise.reject(`HTTP ${r.status}`)) ); return match(result, { ok: (data) => ok({ params, weather: data.current }), err: (msg) => err([{ path: ['weather'], message: String(msg) }]) }); } // Assess launch conditions async function assessLaunch(context: { params: LaunchParams; weather: any }) { const windLimits = { falcon9: 15, atlas5: 12 }; const maxWind = windLimits[context.params.vehicleType]; const actualWind = Math.max(context.weather.wind_speed_10m, context.weather.wind_gusts_10m); return ok({ recommendation: actualWind <= maxWind ? 'GO' : 'NO GO', reason: actualWind <= maxWind ? 'Conditions nominal' : 'Wind exceeds limits', windSpeed: context.weather.wind_speed_10m, maxAllowed: maxWind }); } // Complete pipeline with validation, async operations, and error handling async function evaluateLaunch(input: unknown): Promise> { const validationResult = validate(input, launchSchema); const result = await pipe( validationResult, (r) => andThen(r, fetchWeather), (r) => andThen(r, assessLaunch) ); return match(result, { ok: (decision) => ({ valid: true, data: decision }), err: (errors) => ({ valid: false, errors: formatErrors(errors) }) }); } // Usage const result = await evaluateLaunch({ vehicleType: 'falcon9', payload: 15000, latitude: 28.5721, longitude: -80.648, windowStart: '2025-01-01' }); if (result.valid) { console.log(result.data.recommendation); // "GO" or "NO GO" } else { console.log(result.errors); // { field: "error message" } } ``` -------------------------------- ### Wrapping Try-Catch with fromTry (TypeScript) Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/RECIPES.md Demonstrates how to safely wrap legacy code that uses try-catch blocks with the fromTry function. This converts potential exceptions into a Result type, allowing for consistent error handling within the Railway TS pipeline pattern. ```typescript import { fromTry } from '@railway-ts/pipelines/result'; // Legacy code that throws const parseConfig = (text: string) => JSON.parse(text); // Wrapped const safeParseConfig = (text: string) => fromTry(() => parseConfig(text)); const config = safeParseConfig(input); // Result ``` -------------------------------- ### Object schema validation and type inference Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Demonstrates defining object schemas for validation using `object`, `required`, `optional`, and type-specific validators like `string`, `email`, and `chain`. Includes inferring TypeScript types with `InferSchemaType`. ```typescript import { validate, object, required, optional, chain, string, parseNumber, min, max, email, type InferSchemaType } from '@railway-ts/pipelines/schema'; // Define schema const userSchema = object({ name: required(string()), age: required(chain(parseNumber(), min(18), max(120))), email: required(email()), bio: optional(string()) }); // Infer TypeScript type from schema type User = InferSchemaType; // { name: string; age: number; email: string; bio?: string } // Validate untrusted data const result = validate({ name: 'Alice', age: '25', email: 'alice@example.com' }, userSchema); // Result is Result if (result.ok) { console.log(result.value.age); // 25 (parsed to number) } else { result.error.forEach(e => console.log(`${e.path.join('.')}: ${e.message}`) ); } ``` -------------------------------- ### Sequential Async Steps with andThen (TypeScript) Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/RECIPES.md Illustrates how to perform a sequence of asynchronous operations using the andThen function within a pipe. This pattern is suitable for processing data through multiple asynchronous steps, such as validation, inventory checks, payment processing, and shipment creation. ```typescript import { andThen } from '@railway-ts/pipelines/result'; import { pipe } from '@railway-ts/pipelines/composition'; const processOrder = async (input: unknown) => { const result = await pipe( validate(input, orderSchema), (r) => andThen(r, validateInventory), (r) => andThen(r, chargePayment), (r) => andThen(r, createShipment), ); return match(result, { ok: (order) => ({ success: true, order }), err: (error) => ({ success: false, error }), }); }; ``` -------------------------------- ### Validate and Format Errors in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt This TypeScript snippet demonstrates using the validation module to check input against a schema, formatting errors into a key-value record for user-friendly output. It relies on imports from '@railway-ts/pipelines/schema' and '@railway-ts/pipelines/result'. The function takes an unknown input, validates it, and returns a ValidationResult with either the data or formatted errors, with limitations in handling custom schemas not defined here. ```typescript import { validate, formatErrors, type ValidationResult, type ValidationError } from '@railway-ts/pipelines/schema'; import { match } from '@railway-ts/pipelines/result'; async function processInput(input: unknown): Promise> { const result = validate(input, userSchema); return match>(result, { ok: (data) => ({ valid: true, data }), err: (errors) => ({ valid: false, errors: formatErrors(errors) }) }); } // formatErrors converts ValidationError[] to Record const response = await processInput({ name: '', age: '15' }); if (!response.valid) { console.log(response.errors); // { // 'name': 'String cannot be empty', // 'age': 'Must be at least 18' // } } ``` -------------------------------- ### Point-Free Composition for Option Pipelines (TypeScript) Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/RECIPES.md Illustrates the application of point-free composition to Option type pipelines using helper functions like mapWith and flatMapWith. This pattern facilitates cleaner code by allowing direct application of transformation and chaining operations on Option values without explicit lambda wrappers. ```typescript import { map, flatMap, filter, tap } from '@railway-ts/pipelines/option'; import type { Option } from '@railway-ts/pipelines/option'; const mapWith = (fn: (value: T) => U) => (option: Option): Option => map(option, fn); const flatMapWith = (fn: (value: T) => Option) => (option: Option): Option => flatMap(option, fn); const filterWith = (predicate: (value: T) => boolean) => (option: Option): Option => filter(option, predicate); const tapWith = (fn: (value: T) => void) => (option: Option): Option => tap(option, fn); ``` -------------------------------- ### Validator modifiers and constraints for schema validation Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Illustrates using validator modifiers like `optional`, `nullable`, `emptyAsOptional`, and `chain` to build complex validation rules. Includes constraints such as `minLength`, `maxLength`, `pattern`, `between`, and `nonEmpty`. ```typescript import { object, required, optional, nullable, emptyAsOptional, chain, string, minLength, maxLength, pattern, nonEmpty, parseNumber, integer, between } from '@railway-ts/pipelines/schema'; const profileSchema = object({ // Required field username: required(chain(string(), nonEmpty(), minLength(3), maxLength(20))), // Optional field (can be undefined) bio: optional(chain(string(), maxLength(500))), // Empty string becomes undefined displayName: emptyAsOptional(string()), // Must be exactly null deletedAt: nullable(), // Chained constraints age: required(chain(parseNumber(), integer(), between(13, 150))), // Pattern matching zipCode: required(chain(string(), pattern(/^\d{5}$/, 'Must be 5 digits'))) }); const result = validate({ username: 'alice123', bio: '', displayName: '', deletedAt: null, age: '25', zipCode: '12345' }, profileSchema); // bio becomes undefined, displayName becomes undefined ``` -------------------------------- ### Transforming Result Values with map, mapErr, flatMap, and bimap in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Demonstrates transforming Result values: 'map' transforms success values, 'mapErr' transforms error values, 'flatMap' chains operations returning Results, and 'bimap' transforms both success and error branches. Uses 'pipe' for composition. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { ok, err, map, mapErr, flatMap, bimap } from '@railway-ts/pipelines/result'; // Map transforms success values const divide = (a: number, b: number) => b === 0 ? err('Division by zero') : ok(a / b); const doubled = pipe( divide(10, 2), (r) => map(r, (n) => n * 2) ); // ok(10) // mapErr transforms error values const withTypedError = pipe( err('failed'), (r) => mapErr(r, (msg) => new Error(msg)) ); // err(Error('failed')) // bimap transforms both branches const result = bimap( ok(5), (value) => value.toString(), (error) => new Error(error) ); // ok("5") ``` -------------------------------- ### Wrap Promises with Railway TS Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/RECIPES.md Safely wrap asynchronous functions that return Promises using `fromPromise`. This converts potential rejections into `Result` types, allowing for functional error handling. It takes an async function as input and returns a new async function that resolves to a `Result`. ```typescript import { fromPromise } from '@railway-ts/pipelines/result'; // Legacy async code const fetchUser = async (id: string) => { const response = await fetch(`/api/users/${id}`); return response.json(); }; // Wrapped const safeFetchUser = async (id: string) => fromPromise(fetchUser(id)); const user = await safeFetchUser('123'); // Result ``` -------------------------------- ### Combining Multiple Option Values in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Demonstrates the 'combine' function for aggregating multiple Option values. If all Options are 'some', it returns a 'some' containing an array of the values. If any Option is 'none', it returns 'none', short-circuiting the process. Preserves tuple types. ```typescript import { combine } from '@railway-ts/pipelines/option'; // All Options must be Some for success const coords = combine([some(10), some(20), some(30)]); // some([10, 20, 30]) with preserved tuple type const withNone = combine([some(1), none(), some(3)]); // none() - short-circuits on first None // Type-safe tuple access if (isSome(coords)) { const [x, y, z] = coords.value; // Inferred as [number, number, number] } ``` -------------------------------- ### Safely Unwrapping Option Values in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt Shows methods for safely extracting values from Option types: 'unwrapOr' provides a default value, 'unwrapOrElse' computes a default lazily, and 'match' allows for pattern matching on both 'some' and 'none' cases. ```typescript import { unwrapOr, unwrapOrElse, match } from '@railway-ts/pipelines/option'; // Provide default value const age = unwrapOr(none(), 18); // 18 // Compute default lazily const computed = unwrapOrElse(none(), () => { return 'default-' + Date.now(); }); // Pattern match on both branches const message = match(some(100), { some: (value) => `Found: ${value}`, none: () => 'Nothing found' }); // "Found: 100" ``` -------------------------------- ### Wrap Nullable Returns with Railway TS Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/RECIPES.md Handle functions that might return `null` or `undefined` using `fromNullable`. This utility converts such return values into `Option` types, enabling safer handling of potentially missing data. It takes a function that returns a value or null and returns a function yielding an `Option`. ```typescript import { fromNullable } from '@railway-ts/pipelines/option'; // Legacy code that returns null const findUser = (id: string): User | null => { /* ... */ }; // Wrapped const safeFindUser = (id: string) => fromNullable(findUser(id)); const user = safeFindUser('123'); // Option ``` -------------------------------- ### Pipe for Immediate Execution in TypeScript Source: https://context7.com/sakobu/railway-ts-pipelines/llms.txt This TypeScript code snippet showcases the pipe function for composing operations that execute immediately on values, including Results and Options. It imports functions from '@railway-ts/pipelines/composition' and '@railway-ts/pipelines/result', taking initial values and applying transformations sequentially. The pipe handles both Result types for error propagation and plain values, but is limited to direct execution without reusability. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { ok, map, flatMap } from '@railway-ts/pipelines/result'; // Execute transformations immediately const result = pipe( ok(5), (r) => map(r, (x) => x * 2), (r) => map(r, (x) => x + 1), (r) => flatMap(r, (x) => x > 10 ? ok(x) : err('Too small')) ); // ok(11) // Works with any values, not just Result/Option const simple = pipe( 10, (n) => n * 2, (n) => n.toString(), (s) => s.padStart(5, '0') ); // "00020" ``` -------------------------------- ### Referential Transparency with this: void Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/ADVANCED.md Forces functions to be referentially transparent by preventing this context usage. Avoids bugs from lost context; inputs are Results and functions, outputs are transformed Results. Requires TypeScript; enforces calling as pure functions. ```typescript export function map(this: void, result: Result, fn: (value: T) => U): Result { // ... } ``` -------------------------------- ### Combine Multiple Data Sources with Railway TS Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/RECIPES.md Fetch data from multiple asynchronous sources concurrently and combine their results using `Promise.all` and `combine`. The `combine` function from `@railway-ts/pipelines/result` ensures that if any of the individual fetches fail, the entire operation results in an error. If all succeed, it returns a `Result` containing an array of all successful values. ```typescript import { combine } from '@railway-ts/pipelines/result'; const fetchUserData = async (userId: string) => { const [profile, settings, preferences] = await Promise.all([ fetchProfile(userId), fetchSettings(userId), fetchPreferences(userId), ]); // Combine all results, fail if any failed return combine([profile, settings, preferences]); // Result<[Profile, Settings, Preferences], Error> }; const data = await fetchUserData('123'); match(data, { ok: ([profile, settings, preferences]) => { // All three succeeded, full type safety return { profile, settings, preferences }; }, err: (error) => { // One or more failed return null; }, }); ``` -------------------------------- ### Type Guards for Result Narrowing Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/ADVANCED.md Shows narrowing with isOk and isErr for safe property access. Prevents undefined errors; requires importing guards. Input is Result; outputs are value or error based on check. ```typescript if (isOk(result)) { // TypeScript knows result.value exists console.log(result.value); } if (isErr(result)) { // TypeScript knows result.error exists console.log(result.error); } ``` -------------------------------- ### Root Imports in TypeScript Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/README.md Alternative import pattern from root module. Automatically adds type suffixes to avoid naming collisions between similar functions in different modules. ```typescript import { mapOption, mapResult, pipe, ok, validate } from '@railway-ts/pipelines'; ``` -------------------------------- ### Result Type: Explicit Error Handling Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/README.md Shows how to use the Result type for explicit error handling without exceptions. It demonstrates creating Result values, transforming successful outcomes with 'map', and handling both success and error cases with 'match'. ```typescript import { pipe } from '@railway-ts/pipelines/composition'; import { ok, err, map, match } from '@railway-ts/pipelines/result'; const divide = (a: number, b: number) => (b === 0 ? err('div by zero') : ok(a / b)); const result = pipe(divide(10, 2), (r) => map(r, (x) => x * 3)); match(result, { ok: (value) => console.log(value), err: (error) => console.error(error), }); // Output: 15 ``` -------------------------------- ### Preventing Duck Typing with Branding Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/ADVANCED.md Demonstrates how symbol branding prevents mistakes from unbranded objects being treated as Options. Without branding, plain objects could cause type safety issues; with it, TypeScript errors on missing symbols. No dependencies beyond TypeScript; input is plain object, output is type error or proper construction. ```typescript const fakeOption = { some: true, value: 42 }; // Could be accidentally treated as Option due to structural typing const fakeOption = { some: true, value: 42 }; // Type error: missing [OPTION_BRAND] symbol // Only way to create valid Options is through some() or none() ``` -------------------------------- ### Mixing Sync and Async Operations in a Pipeline (TypeScript) Source: https://github.com/sakobu/railway-ts-pipelines/blob/main/docs/RECIPES.md Shows how to combine synchronous and asynchronous operations within a single pipeline using functions like map and andThen. This pattern allows for seamless integration of both types of operations, maintaining a consistent flow for data processing, including database fetches and transformations. ```typescript const process = async (input: unknown) await pipe( validate(input, schema), // sync (r) => map(r, enrichData), // sync (r) => andThen(r, fetchDB), // async (r) => map(r, transform), // sync (r) => andThen(r, saveDB), // async ); ```