### Run Railway-ts Examples Source: https://github.com/sakobu/railway-ts/blob/main/README.md Steps to clone the railway-ts repository, install dependencies, and run the provided examples, including specific examples for Option, Result, interop, and composition utilities. ```bash # Clone the repository git clone https://github.com/sakobu/railway-ts.git cd railway-ts # Install dependencies and run examples bun install bun run examples/index.ts # Or run specific examples bun run examples/option/option-examples.ts # Option type examples bun run examples/result/result-examples.ts # Result type examples bun run examples/interop/interop-examples.ts # Interoperability examples bun run examples/composition/curry-basics.ts # Curry function examples bun run examples/composition/tupled-basics.ts # Tupled function examples bun run examples/composition/advanced-composition.ts # Advanced composition patterns ``` -------------------------------- ### Quick Start: Option Type Example Source: https://github.com/sakobu/railway-ts/blob/main/README.md A concise example demonstrating the usage of the Option type with `pipe`, `some`, `mapOption`, `filterOption`, and `unwrapOptionOr` for safe nullable handling. ```typescript import { pipe, some, mapOption, filterOption, unwrapOptionOr } from "@railway-ts/core"; const value = pipe( some(21), (o) => mapOption(o, (n) => n * 2), // Some(42) (o) => filterOption(o, (n) => n >= 40), (o) => unwrapOptionOr(o, 0), // 42 ); ``` -------------------------------- ### Install @railway-ts/core Package Source: https://github.com/sakobu/railway-ts/blob/main/README.md Instructions for installing the @railway-ts/core package using various package managers like npm, yarn, pnpm, and bun. ```bash # npm npm install @railway-ts/core # yarn yarn add @railway-ts/core # pnpm pnpm add @railway-ts/core # bun bun add @railway-ts/core ``` -------------------------------- ### Quick Start: Result Type Example Source: https://github.com/sakobu/railway-ts/blob/main/README.md An example showcasing the Result type with `pipe`, `ok`, `err`, `mapResult`, `filterResult`, and `matchResult` for explicit error handling. ```typescript import { pipe, ok, err, mapResult, filterResult, matchResult } from "@railway-ts/core"; const message = pipe( ok(10), (r) => mapResult(r, (n) => n * 4), // Ok(40) (r) => filterResult(r, (n) => n >= 42, "too small"), (r) => matchResult(r, { ok: (n) => `ok: ${n}`, err: (e) => `error: ${e}`, }), ); ``` -------------------------------- ### Create Option Types with `some` and `none` in TypeScript Source: https://context7.com/sakobu/railway-ts/llms.txt Demonstrates how to create Option types using `some` to wrap a value and `none` to represent absence. Includes type-safe usage in functions and examples of the output for both `some` and `none` Option instances. ```typescript import { some, none, type Option } from "@railway-ts/core"; // Create an Option containing a value const hasValue: Option = some(42); console.log(hasValue); // { some: true, value: 42, [OPTION_BRAND]: 'some' } // Create an Option representing absence const noValue: Option = none(); console.log(noValue); // { some: false, [OPTION_BRAND]: 'none' } // Type-safe usage in functions function findUser(id: number): Option<{ name: string; email: string }> { if (id === 1) { return some({ name: "Alice", email: "alice@example.com" }); } return none(); } const user1 = findUser(1); // Some({ name: 'Alice', email: 'alice@example.com' }) const user2 = findUser(999); // None ``` -------------------------------- ### Compose Functions with pipe and flow in TypeScript Source: https://context7.com/sakobu/railway-ts/llms.txt Demonstrates building data transformation pipelines using `pipe` for immediate, left-to-right execution and `flow` for creating reusable, deferred execution functions. Handles various data types and includes examples with Railway's `Result` and `Option` types. Input can be any value, output depends on the final transformation. ```typescript import { pipe, flow } from "@railway-ts/core"; import { Result, ok, err, mapResult, fromNullable, mapOption, filterOption, unwrapOr } from "@railway-ts/core"; // pipe: immediate execution (left-to-right) const result = pipe( 5, (n) => n * 2, // 10 (n) => n + 3, // 13 (n) => n.toString(), // "13" (s) => s.padStart(5, "0"), // "00013" ); console.log(result); // "00013" // flow: deferred execution (creates reusable function) const processNumber = flow( (n: number) => n * 2, (n) => n + 3, (n) => n.toString(), (s) => s.padStart(5, "0"), ); console.log(processNumber(5)); // "00013" console.log(processNumber(10)); // "00023" // Real-world: data processing pipeline type RawData = { value: string }; type ProcessedData = { normalized: number }; const normalizeData = (raw: RawData): number => Number(raw.value.trim()); const validateRange = (n: number): Result => n >= 0 && n <= 100 ? ok(n) : err("Value out of range"); const wrapData = (n: number): ProcessedData => ({ normalized: n }); // Using pipe for single execution const singleResult = pipe( { value: " 42 " }, normalizeData, (n) => validateRange(n), (r) => mapResult(r, wrapData), ); console.log(singleResult); // Ok({ normalized: 42 }) // Using flow for reusable processor const dataProcessor = flow( normalizeData, (n: number) => validateRange(n), (r) => mapResult(r, wrapData), ); console.log(dataProcessor({ value: "50" })); // Ok({ normalized: 50 }) console.log(dataProcessor({ value: "150" })); // Err("Value out of range") // Complex pipeline with Options const processUserInput = flow( (s: string) => s.trim(), (s) => fromNullable(s === "" ? null : s), (opt) => mapOption(opt, (s) => s.toUpperCase()), (opt) => filterOption(opt, (s) => s.length <= 50), (opt) => unwrapOr(opt, "DEFAULT"), ); console.log(processUserInput("hello")); // "HELLO" console.log(processUserInput("")); // "DEFAULT" console.log(processUserInput("a".repeat(100))); // "DEFAULT" (too long) ``` -------------------------------- ### Convert Between Option and Result Types (TypeScript) Source: https://context7.com/sakobu/railway-ts/llms.txt Illustrates converting `Option` to `Result` using `mapToResult` to add error context to `None` values, and converting `Result` to `Option` using `mapToOption` to discard error details. Includes practical examples for handling optional fields and safe parsing. ```typescript import { some, none, ok, err, mapToResult, mapToOption, pipe, mapResult, matchResult } from "@railway-ts/core"; // Option → Result: add error context to None const someValue = some(42); const someResult = mapToResult(someValue, "Value was missing"); console.log(someResult); // Ok(42) const noneValue = none(); const noneResult = mapToResult(noneValue, "Value was missing"); console.log(noneResult); // Err("Value was missing") // Result → Option: drop error details const okResult = ok(123); const okOption = mapToOption(okResult); console.log(okOption); // Some(123) const errResult = err("Something failed"); const errOption = mapToOption(errResult); console.log(errOption); // None // Real-world: optional field to required field type UserInput = { name?: string; email?: string }; type ValidatedUser = { name: string; email: string }; function validateUser(input: UserInput): Result { const errors: string[] = []; const nameResult = mapToResult(fromNullable(input.name), "Name is required"); const emailResult = mapToResult(fromNullable(input.email), "Email is required"); return pipe( combineAllResult([nameResult, emailResult]), (r) => mapResult(r, ([name, email]) => ({ name, email })), ); } const valid = validateUser({ name: "Alice", email: "alice@example.com" }); console.log(valid); // Ok({ name: 'Alice', email: 'alice@example.com' }) const invalid = validateUser({ name: "Bob" }); matchResult(invalid, { ok: (user) => console.log("Valid user:", user), err: (errors) => console.log("Errors:", errors), // ["Email is required"] }); // Result → Option: when you don't care about error details function tryParseNumber(s: string): Option { const result = fromTry(() => { const n = Number(s); if (!Number.isFinite(n)) throw new Error("Invalid"); return n; }); return mapToOption(result); // Discard error message, just know it failed } console.log(tryParseNumber("42")); // Some(42) console.log(tryParseNumber("abc")); // None ``` -------------------------------- ### Pattern Match Results with match in TypeScript Source: https://context7.com/sakobu/railway-ts/llms.txt Provides examples of using the `matchResult` function to exhaustively handle both success (`ok`) and error (`err`) cases of a Result type. This ensures all possible outcomes are addressed, preventing unhandled states. ```typescript import { ok, err, match as matchResult, pipe, mapResult } from "@railway-ts/core"; // Basic pattern matching const result = ok(42); const message = matchResult(result, { ok: (value) => `Success: ${value}`, err: (error) => `Error: ${error}`, }); console.log(message); // "Success: 42" const errorResult = err("Network timeout"); const errorMessage = matchResult(errorResult, { ok: (value) => `Success: ${value}`, err: (error) => `Error: ${error}`, }); console.log(errorMessage); // "Error: Network timeout" // Real-world: HTTP response handling type ApiResponse = { data: any; status: number }; type ApiError = { code: number; message: string }; function fetchUser(id: number): Result { if (id < 0) { return err({ code: 400, message: "Invalid user ID" }); } return ok({ data: { id, name: "Alice" }, status: 200 }); } const response = fetchUser(1); const output = matchResult(response, { ok: (res) => `User data: ${JSON.stringify(res.data)}`, err: (e) => `API Error ${e.code}: ${e.message}`, }); console.log(output); // "User data: {"id":1,"name":"Alice"}" const errorResponse = fetchUser(-1); const errorOutput = matchResult(errorResponse, { ok: (res) => `User data: ${JSON.stringify(res.data)}`, err: (e) => `API Error ${e.code}: ${e.message}`, }); console.log(errorOutput); // "API Error 400: Invalid user ID" ``` -------------------------------- ### Combine Results with Different Error Strategies (TypeScript) Source: https://context7.com/sakobu/railway-ts/llms.txt Demonstrates aggregating multiple Result objects using `combine` for fail-fast error handling and `combineAll` for collecting all errors. It covers empty array handling, type-safe tuple preservation, and a real-world form validation example. ```typescript import { ok, err, combine as combineResult, combineAll as combineAllResult, isOk } from "@railway-ts/core"; // combine: fail-fast (returns first error) const allSuccess = combineResult([ok(1), ok(2), ok(3)]); console.log(allSuccess); // Ok([1, 2, 3]) const hasError = combineResult([ok(1), err("error1"), err("error2")]); console.log(hasError); // Err("error1") - stops at first error // Empty array handling const empty = combineResult([]); console.log(empty); // Ok([]) // Type-safe tuple preservation const tuple = combineResult([ok(1), ok("hello"), ok(true)] as const); if (isOk(tuple)) { const [num, str, bool] = tuple.value; // Types: [number, string, boolean] console.log(num, str, bool); // 1, "hello", true } // combineAll: collect all errors const allErrors = combineAllResult([ok(1), err("error1"), ok(3), err("error2")]); console.log(allErrors); // Err(["error1", "error2"]) const someSuccess = combineAllResult([ok(1), ok(2), ok(3)]); console.log(someSuccess); // Ok([1, 2, 3]) // Real-world: form validation with all errors type ValidationError = { field: string; message: string }; function validateName(name: string): Result { return name.length > 0 ? ok(name) : err({ field: "name", message: "Name is required" }); } function validateAge(age: number): Result { return age >= 18 ? ok(age) : err({ field: "age", message: "Must be 18 or older" }); } function validateEmail(email: string): Result { return email.includes("@") ? ok(email) : err({ field: "email", message: "Invalid email format" }); } // Collect all validation errors at once const validationResults = combineAllResult([ validateName(""), validateAge(16), validateEmail("invalid"), ]); matchResult(validationResults, { ok: ([name, age, email]) => console.log("Valid form:", { name, age, email }), err: (errors) => { console.log("Validation errors:"); errors.forEach((e) => console.log(` ${e.field}: ${e.message}`)); }, }); // Output: // Validation errors: // name: Name is required // age: Must be 18 or older // email: Invalid email format ``` -------------------------------- ### Convert Nullable Values to Options with `fromNullable` in TypeScript Source: https://context7.com/sakobu/railway-ts/llms.txt Shows how to use `fromNullable` to convert JavaScript's `null` or `undefined` into `Option` types, enabling type-safe handling. Includes examples of checking for values with `isSome` and providing defaults with `unwrapOr` for property and array access. ```typescript import { fromNullable, isSome, unwrapOr } from "@railway-ts/core"; // Convert nullable values to Options const maybeString: string | null = getUserInput(); // Assume getUserInput() can return null const option = fromNullable(maybeString); if (isSome(option)) { console.log("Got value:", option.value); // Type-safe access } else { console.log("No value provided"); } // Real-world example: safe property access type User = { name?: string; email?: string }; const user: User = { name: "Bob" }; const emailOption = fromNullable(user.email); const email = unwrapOr(emailOption, "no-email@example.com"); console.log(email); // "no-email@example.com" // Array access safety const items = ["apple", "banana", "cherry"]; const item = fromNullable(items[10]); // None for out-of-bounds access const result = unwrapOr(item, "default item"); console.log(result); // "default item" ``` -------------------------------- ### Immediate Execution with `pipe` in @railway-ts/core Source: https://github.com/sakobu/railway-ts/blob/main/README.md Illustrates the immediate execution of a sequence of functions using the `pipe` utility from the @railway-ts/core library. The initial value is passed through each function in succession, with the output of one function becoming the input of the next. ```typescript const result = pipe( 5, (n) => n * 2, // 10 (n) => n + 1, // 11 (n) => n.toString(), // "11" ); ``` -------------------------------- ### Option Functions API Reference Source: https://github.com/sakobu/railway-ts/blob/main/README.md Reference for Option-related functions, including creation, type guards, transformations, unwrapping, and conversion. ```APIDOC ## API Reference - Option Functions This section provides a detailed reference for the `Option` related functions in `@railway-ts/core`. | Function | Description | | --------------------------------------------------------- | ---------------------------- | | `some(value: T)` | Create Option with value | | `none()` | Create empty Option | | `isSome(o: Option)` | Type guard for Some | | `isNone(o: Option)` | Type guard for None | | `mapOption(o: Option, fn: T => U)` | Transform value | | `flatMapOption(o: Option, fn: T => Option)` | Chain operations | | `filterOption(o: Option, pred: T => boolean)` | Conditional keep | | `unwrapOption(o: Option, errorMsg?: string)` | Get value or throw | | `unwrapOptionOr(o: Option, default: T)` | Get value or default | | `unwrapOptionOrElse(o: Option, defaultFn: () => T)` | Get value or compute default | | `fromNullableOption(val: T | null | undefined)` | Convert nullable | | `combineOption(opts: Option[])` | All-or-nothing combine | | `matchOption(o: Option, patterns)` | Pattern match | | `tapOption(o: Option, fn: (value: T) => void)` | Execute side effect if Some | | `mapToResult(o: Option, error: E)` | Convert Option to Result | ``` -------------------------------- ### Composition Utilities Source: https://github.com/sakobu/railway-ts/blob/main/README.md Provides a set of utilities for composing functions, including immediate execution with `pipe`, function composition with `flow`, and partial application with `curry`. ```APIDOC ## Composition Utilities This section details the function composition utilities available in `@railway-ts/core`. ### `pipe` - Immediate Execution Applies a sequence of functions to an initial value, executing them immediately. **Example:** ```typescript import { pipe } from "@railway-ts/core"; const result = pipe( 5, (n) => n * 2, // 10 (n) => n + 1, // 11 (n) => n.toString(), // "11" ); // result is "11" ``` ### `flow` - Function Composition Creates a new function that composes multiple functions together. The composed function takes an argument and passes it through the sequence of functions. **Example:** ```typescript import { flow } from "@railway-ts/core"; const processNumber = flow( (n: number) => n * 2, (n) => n + 1, (n) => n.toString(), ); const result = processNumber(5); // "11" ``` ### `curry` - Partial Application and Composition Partially applies arguments to a function, returning a new function that accepts the remaining arguments. Can be used with `pipe` for composition. **Example:** ```typescript import { pipe, curry } from "@railway-ts/core"; const add = (a: number, b: number) => a + b; const multiply = (a: number, b: number) => a * b; const value = pipe( 10, curry(add)(5), // 15 curry(multiply)(2), // 30 ); // value is 30 const add5 = curry(add)(5); const double = curry(multiply)(2); const also = pipe(10, add5, double); // 30 ``` ### `uncurry` - Convert Curried to Multi-Arg Converts a curried function (a function that takes arguments one at a time) into a function that accepts multiple arguments directly. **Example:** ```typescript import { uncurry } from "@railway-ts/core"; const clamp = (min: number) => (max: number) => (value: number) => Math.min(max, Math.max(min, value)); const normalClamp = uncurry(clamp); normalClamp(0, 100, 150); // 100 ``` ### `tupled` - Adapt Multi-Arg to Tuple Input Adapts a multi-argument function to accept a single tuple argument. **Example:** ```typescript import { pipe, tupled } from "@railway-ts/core"; const calculateTotal = (price: number, tax: number, discount: number) => price * (1 + tax) - discount; const total = pipe( [100, 0.1, 10], tupled(calculateTotal), ); // total is 100 ``` ### `untupled` - Adapt Tuple Input to Multi-Arg Adapts a function that accepts a single tuple argument to accept multiple arguments. **Example:** ```typescript import { untupled } from "@railway-ts/core"; const divmod = ([n, d]: [number, number]): [number, number] => [Math.floor(n / d), n % d]; const normalDivmod = untupled(divmod); normalDivmod(20, 7); // [2, 6] ``` ``` -------------------------------- ### Partial Application and Composition with `curry` in @railway-ts/core Source: https://github.com/sakobu/railway-ts/blob/main/README.md Demonstrates the use of `curry` from @railway-ts/core for partial application and composition. `curry` transforms a multi-argument function into a sequence of functions, each taking a single argument. This allows for creating specialized functions and composing them using `pipe`. ```typescript import { pipe, curry } from "@railway-ts/core"; const add = (a: number, b: number) => a + b; const multiply = (a: number, b: number) => a * b; const value = pipe( 10, curry(add)(5), // 15 curry(multiply)(2), // 30 ); const add5 = curry(add)(5); const double = curry(multiply)(2); const also = pipe(10, add5, double); // 30 ``` -------------------------------- ### Optimized Imports with Tree-Shaking in @railway-ts/core Source: https://github.com/sakobu/railway-ts/blob/main/README.md Shows how to leverage tree-shaking with the @railway-ts/core library by importing only the necessary functions. This can be done either by importing from the root or directly from specific module paths, reducing the final bundle size. ```typescript // Import with prefixes (recommended) import { some, mapOption, ok, mapResult, pipe } from "@railway-ts/core"; // Import directly from modules import { some, map } from "@railway-ts/core/option"; import { ok, err } from "@railway-ts/core/result"; import { pipe, flow } from "@railway-ts/core/utils"; ``` -------------------------------- ### Safe Nullable Handling with Option Source: https://github.com/sakobu/railway-ts/blob/main/README.md Demonstrates how to safely handle nullable or undefined values using the `Option` type. It shows how to chain operations like sanitization and parsing, returning either `some` value or `none` if any step fails. ```typescript import { pipe, fromNullableOption, mapOption, flatMapOption, matchOption, some, none } from "@railway-ts/core"; const sanitize = (s: string) => s.trim(); const parseAge = (s: string) => { const n = Number(s); return Number.isFinite(n) && n >= 0 ? some(n) : none(); }; const processInput = (input: string | null | undefined) => pipe( fromNullableOption(input), (o) => mapOption(o, sanitize), (o) => flatMapOption(o, parseAge), (o) => matchOption(o, { some: (age) => `Age: ${age}`, none: () => "Invalid age", }), ); ``` -------------------------------- ### Multi-Argument Functions with Curry and Tupled Source: https://github.com/sakobu/railway-ts/blob/main/README.md Shows how to work with multi-argument functions using `pipe` in conjunction with `curry` for partial application and `tupled` when data is in pairs. This allows for functional composition of functions that originally accepted multiple arguments. ```typescript import { pipe, curry, tupled } from "@railway-ts/core"; const add = (a: number, b: number) => a + b; const divide = (dividend: number, divisor: number) => dividend / divisor; // Use curry for partial application const result1 = pipe( 10, curry(add)(5), // 15 curry(divide)(3), // 5 ); // Use tupled when data comes as pairs const result2 = pipe( [10, 2], tupled(divide), // 5 (n) => n * 2, // 10 ); ``` -------------------------------- ### Transform Options with `map` and `flatMap` in TypeScript Source: https://context7.com/sakobu/railway-ts/llms.txt Illustrates how to transform values within `Option` types using `map` and chain operations that return `Option` types using `flatMap`. Demonstrates usage with `pipe` for composing functions and handling valid, invalid, and missing values. ```typescript import { some, none, map as mapOption, flatMap as flatMapOption, pipe } from "@railway-ts/core"; // map: transform the value inside an Option const option = some(21); const doubled = mapOption(option, (n) => n * 2); console.log(doubled); // Some(42) const emptyOption = none(); const stillNone = mapOption(emptyOption, (n) => n * 2); console.log(stillNone); // None // flatMap: chain operations that return Options function parseAge(s: string): Option { const n = Number(s); return Number.isFinite(n) && n >= 0 ? some(n) : none(); } function isAdult(age: number): Option { return age >= 18 ? some("adult") : none(); } // Chain with flatMap const validAdult = pipe( some("25"), (opt) => flatMapOption(opt, parseAge), (opt) => flatMapOption(opt, isAdult), ); console.log(validAdult); // Some("adult") const tooYoung = pipe( some("15"), (opt) => flatMapOption(opt, parseAge), (opt) => flatMapOption(opt, isAdult), ); console.log(tooYoung); // None const invalid = pipe( some("not a number"), (opt) => flatMapOption(opt, parseAge), (opt) => flatMapOption(opt, isAdult), ); console.log(invalid); // None ``` -------------------------------- ### Interop Between Option and Result Source: https://github.com/sakobu/railway-ts/blob/main/README.md Demonstrates how to convert between Option and Result types, injecting an error for None or dropping error information when converting Result to Option. ```APIDOC ## Interop Between Option and Result This section explains how to convert between `Option` and `Result` types within the `@railway-ts/core` library. ### `mapToResult(o: Option, error: E): Result` Converts an `Option` to a `Result`. If the `Option` is `Some(value)`, it returns `Ok(value)`. If the `Option` is `None`, it returns `Err(error)`. **Example:** ```typescript import { mapToResult, some, none, ok, err } from "@railway-ts/core"; const r1 = mapToResult(some(123), "missing"); // Ok(123) const r2 = mapToResult(none(), "missing"); // Err("missing") ``` ### `mapToOption(r: Result): Option` Converts a `Result` to an `Option`. If the `Result` is `Ok(value)`, it returns `Some(value)`. If the `Result` is `Err(error)`, it returns `None`, discarding the error information. **Example:** ```typescript import { mapToOption, ok, err } from "@railway-ts/core"; const o1 = mapToOption(ok(10)); // Some(10) const o2 = mapToOption(err("boom")); // None ``` ``` -------------------------------- ### Convert Option to Result and Vice Versa with @railway-ts/core Source: https://github.com/sakobu/railway-ts/blob/main/README.md Demonstrates how to convert between Option and Result types using `mapToResult` and `mapToOption` functions from the @railway-ts/core library. `mapToResult` injects an error value if the Option is `None`, while `mapToOption` drops error information if the Result is `Err`. ```typescript import { mapToResult, mapToOption, some, none, ok, err } from "@railway-ts/core"; // Option → Result (inject error for None) const r1 = mapToResult(some(123), "missing"); // Ok(123) const r2 = mapToResult(none(), "missing"); // Err("missing") // Result → Option (drop error info) const o1 = mapToOption(ok(10)); // Some(10) const o2 = mapToOption(err("boom")); // None ``` -------------------------------- ### Combining Multiple Options and Results Source: https://github.com/sakobu/railway-ts/blob/main/README.md Shows how to combine multiple `Option` or `Result` values. `combineOption` returns `some` only if all inputs are `some`. `combineResult` fails fast on the first `err`. `combineAllResult` collects all errors. ```typescript import { combineOption, combineResult, combineAllResult } from "@railway-ts/core"; // Option: all-or-nothing combineOption([some(1), some(2), some(3)]); // Some([1,2,3]) combineOption([some(1), none(), some(3)]); // None // Result: fail-fast combineResult([ok(1), ok(2), ok(3)]); // Ok([1,2,3]) combineResult([ok(1), err("boom"), ok(3)]); // Err("boom") // Result: collect all errors combineAllResult([ok(1), err("a"), err("b"), ok(2)]); // Err(["a","b"]) ``` -------------------------------- ### Async Chaining with Result and andThen Source: https://github.com/sakobu/railway-ts/blob/main/README.md Demonstrates asynchronous chaining of operations using `andThen` with the `Result` type. This allows for sequential execution of both synchronous and asynchronous functions that return `Result`, with errors short-circuiting the chain. ```typescript import { pipe, ok, err, andThen, unwrapResultOr } from "@railway-ts/core"; // Chain async operations seamlessly type User = { id: number; name: string }; type Post = { id: number; userId: number; title: string }; // Chain async operations seamlessly const fetchUser = async (id: number) => (id > 0 ? ok({ id, name: "Alice" }) : err("Invalid ID")); const fetchPosts = async (user: User) => ok([{ id: 1, userId: user.id, title: "Hello" }]); const result = await pipe( ok(1), (r) => andThen(r, fetchUser), (p) => andThen(p, fetchPosts), (pr) => pr.then((r) => unwrapResultOr(r, [])), ); // Works with sync and async step functions const process = await andThen( ok(42), (n) => ok(n * 2), // sync step returning Result ); // Errors short-circuit the chain const failed = await andThen(err("initial error"), async (value) => { // This never runs return ok(value); }); // Err("initial error") ``` -------------------------------- ### Currying and Uncurrying Functions in TypeScript Source: https://context7.com/sakobu/railway-ts/llms.txt Illustrates transforming functions using `curry` to enable partial application and composition, and `uncurry` to convert curried functions back to their multi-argument form. This is useful for creating specialized functions and integrating with composition pipelines like `pipe` and `flow`. Input is a function, output is a transformed function or the result of applying it. ```typescript import { curry, uncurry, pipe, flow } from "@railway-ts/core"; // curry: convert multi-arg to unary chain const add = (a: number, b: number) => a + b; const multiply = (a: number, b: number) => a * b; const curriedAdd = curry(add); const curriedMultiply = curry(multiply); // Use in pipe for composition const result = pipe( 10, curriedAdd(5), // 15 curriedMultiply(2), // 30 ); console.log(result); // 30 // Partial application const add5 = curriedAdd(5); const double = curriedMultiply(2); console.log(add5(10)); // 15 console.log(double(10)); // 20 // Compose partial applications const addThenDouble = flow(add5, double); console.log(addThenDouble(10)); // 30 (10 + 5 = 15, 15 * 2 = 30) // uncurry: convert curried back to multi-arg const clamp = (min: number) => (max: number) => (value: number) => Math.min(max, Math.max(min, value)); const normalClamp = uncurry(clamp); console.log(normalClamp(0, 100, 150)); // 100 console.log(normalClamp(0, 100, -10)); // 0 console.log(normalClamp(0, 100, 50)); // 50 // Real-world: building specialized functions const formatPrice = (currency: string, decimals: number, amount: number): string => `${currency}${amount.toFixed(decimals)}`; const curriedFormatPrice = curry(formatPrice); // Create specialized formatters const formatUSD = curriedFormatPrice("$")(2); const formatEUR = curriedFormatPrice("€")(2); const formatBTC = curriedFormatPrice("₿")(8); console.log(formatUSD(19.99)); // "$19.99" console.log(formatEUR(29.50)); // "€29.50" console.log(formatBTC(0.00012345)); // "₿0.00012345" // Use in data processing pipeline type Product = { name: string; price: number }; const products: Product[] = [ { name: "Coffee", price: 4.50 }, { name: "Tea", price: 3.25 }, ]; const formatted = products.map((p) => pipe(p.price, formatUSD), ); console.log(formatted); // ["$4.50", "$3.25"] ``` -------------------------------- ### Wrapping Promises with fromPromise Source: https://github.com/sakobu/railway-ts/blob/main/README.md Shows how to convert Promises, including those from `fetch` or other async operations, into `Result` types using `fromPromise` or `fromPromiseWithError`. This allows seamless integration of asynchronous operations into functional pipelines. ```typescript import { fromPromise, fromPromiseWithError, matchResult } from "@railway-ts/core"; // Simple case: string errors (recommended) const safeFetch = async (url: string) => { const result = await fromPromise(fetch(url)); return result; // Result }; // Advanced case: custom error types type ApiError = { readonly code: number; readonly message: string }; const safeFetchWithError = async (url: string) => fromPromiseWithError(fetch(url), (e) => ({ code: 500, message: e instanceof Error ? e.message : String(e), })); const result = await safeFetch("/api/data"); matchResult(result, { ok: (response) => console.log("Success:", response.status), err: (error) => console.error(`Error: ${error}`), // error is a string }); ``` -------------------------------- ### Converting Curried to Multi-Arg Functions with `uncurry` in @railway-ts/core Source: https://github.com/sakobu/railway-ts/blob/main/README.md Explains how to use the `uncurry` utility from @railway-ts/core to convert a curried function (one that accepts arguments one at a time) into a function that accepts all its arguments at once. ```typescript import { uncurry } from "@railway-ts/core"; const clamp = (min: number) => (max: number) => (value: number) => Math.min(max, Math.max(min, value)); const normalClamp = uncurry(clamp); normalClamp(0, 100, 150); // 100 ``` -------------------------------- ### Function Composition with `flow` in @railway-ts/core Source: https://github.com/sakobu/railway-ts/blob/main/README.md Shows how to compose functions for later execution using the `flow` utility from @railway-ts/core. `flow` creates a new function that applies a series of transformations sequentially to its input, similar to `pipe` but designed for function creation rather than immediate execution. ```typescript const processNumber = flow( (n: number) => n * 2, (n) => n + 1, (n) => n.toString(), ); const result = processNumber(5); // "11" ``` -------------------------------- ### Wrapping Throwing Functions with fromTry Source: https://github.com/sakobu/railway-ts/blob/main/README.md Demonstrates how to safely execute functions that might throw errors by wrapping them with `fromTry` or `fromTryWithError`. This converts potential exceptions into `Result` types, allowing for functional error handling. ```typescript import { fromTry, fromTryWithError, mapResult } from "@railway-ts/core"; // Simple case: string errors (recommended) const parseJson = (s: string) => fromTry(() => JSON.parse(s)); const safe = mapResult(parseJson('{"x":1}'), (v) => v.x); // Ok(1) const unsafe = parseJson("invalid"); // Err("Unexpected token...") // Advanced case: preserve full Error object for debugging const parseJsonWithError = (s: string) => fromTryWithError(() => JSON.parse(s)); const debugResult = parseJsonWithError("invalid"); // Err(SyntaxError) with stack trace ``` -------------------------------- ### Explicit Error Handling with Result Source: https://github.com/sakobu/railway-ts/blob/main/README.md Illustrates explicit error handling using the `Result` type. It shows how to chain operations that can either succeed (`ok`) or fail (`err`), with errors short-circuiting the pipeline. ```typescript import { pipe, ok, err, flatMapResult, mapResult, matchResult } from "@railway-ts/core"; const ensurePositive = (n: number) => (n > 0 ? ok(n) : err({ reason: "non_positive" })); const calculate = pipe( ok(-4), (r) => flatMapResult(r, ensurePositive), (r) => mapResult(r, (n) => n * 3), (r) => matchResult(r, { ok: (n) => `result: ${n}`, err: (e) => `error: ${e.reason}`, }), ); ``` -------------------------------- ### Adapting Multi-Arg Functions to Tuple Input with `tupled` in @railway-ts/core Source: https://github.com/sakobu/railway-ts/blob/main/README.md Illustrates the use of the `tupled` utility from @railway-ts/core. `tupled` adapts a standard multi-argument function to accept a single tuple argument, making it compatible with functional composition pipelines that pass data structures. ```typescript import { pipe, tupled } from "@railway-ts/core"; const calculateTotal = (price: number, tax: number, discount: number) => price * (1 + tax) - discount; const total = pipe( [100, 0.1, 10], tupled(calculateTotal), // 100 ); ``` -------------------------------- ### Adapting Tuple Input to Multi-Arg Functions with `untupled` in @railway-ts/core Source: https://github.com/sakobu/railway-ts/blob/main/README.md Demonstrates the `untupled` utility from @railway-ts/core, which transforms a function that accepts a single tuple argument into a function that accepts multiple arguments individually. This is useful for integrating tuple-based operations into standard function flows. ```typescript import { untupled } from "@railway-ts/core"; const divmod = ([n, d]: [number, number]): [number, number] => [Math.floor(n / d), n % d]; const normalDivmod = untupled(divmod); normalDivmod(20, 7); // [2, 6] ``` -------------------------------- ### Transform Results with map, mapErr, and flatMap in TypeScript Source: https://context7.com/sakobu/railway-ts/llms.txt Demonstrates how to transform success and error values within Result types using `mapResult` and `mapErrResult`. It also shows how to chain operations that themselves return Results using `flatMapResult` for sequential, safe execution. ```typescript import { ok, err, map as mapResult, mapErr as mapErrResult, flatMap as flatMapResult, pipe } from "@railway-ts/core"; // map: transform success values const result = ok(21); const doubled = mapResult(result, (n) => n * 2); console.log(doubled); // Ok(42) const error = err("failed"); const stillError = mapResult(error, (n) => n * 2); console.log(stillError); // Err("failed") // mapErr: transform error values const serverError = err("INTERNAL_ERROR"); const userFriendly = mapErrResult(serverError, (e) => `Error: ${e}. Please try again.`); console.log(userFriendly); // Err("Error: INTERNAL_ERROR. Please try again.") // flatMap: chain operations that return Results function parseNumber(s: string): Result { const n = Number(s); return Number.isFinite(n) ? ok(n) : err("Invalid number"); } function divideBy(divisor: number): (n: number) => Result { return (n) => (divisor === 0 ? err("Division by zero") : ok(n / divisor)); } // Success chain const success = pipe( ok("42"), (r) => flatMapResult(r, parseNumber), (r) => flatMapResult(r, divideBy(2)), (r) => mapResult(r, Math.round), ); console.log(success); // Ok(21) // Failure chain (stops at first error) const failure = pipe( ok("not a number"), (r) => flatMapResult(r, parseNumber), (r) => flatMapResult(r, divideBy(2)), // Not executed (r) => mapResult(r, Math.round), // Not executed ); console.log(failure); // Err("Invalid number") ``` -------------------------------- ### Extracting Option Values with `unwrapOr` and `match` (TypeScript) Source: https://context7.com/sakobu/railway-ts/llms.txt Safely extract values from Option types using `unwrapOr` for a default value, `unwrapOrElse` for lazy default computation, and `match` for exhaustive pattern matching. These functions help manage the presence or absence of values gracefully. ```typescript import { some, none, unwrapOr, unwrapOrElse, match as matchOption } from "@railway-ts/core"; // unwrapOr: provide a default value const option1 = some(42); const value1 = unwrapOr(option1, 0); console.log(value1); // 42 const option2 = none(); const value2 = unwrapOr(option2, 0); console.log(value2); // 0 // unwrapOrElse: compute default lazily const expensiveDefault = () => { console.log("Computing expensive default..."); return 999; }; const withValue = some(42); console.log(unwrapOrElse(withValue, expensiveDefault)); // 42 (default not computed) const withoutValue = none(); console.log(unwrapOrElse(withoutValue, expensiveDefault)); // Logs: "Computing expensive default..." // Returns: 999 // match: pattern match for exhaustive handling const option = some(42); const message = matchOption(option, { some: (value) => `Found: ${value}`, none: () => "Not found", }); console.log(message); // "Found: 42" const empty = none(); const emptyMessage = matchOption(empty, { some: (value) => `Found: ${value}`, none: () => "Not found", }); console.log(emptyMessage); // "Not found" ```