{"id":20532027,"url":"https://github.com/nozzlegear/railway","last_synced_at":"2025-04-14T06:30:38.799Z","repository":{"id":57132101,"uuid":"142706681","full_name":"nozzlegear/railway","owner":"nozzlegear","description":"Functional helpers including `Async` and `Result` to help enable things like \"railway-oriented\" programming. Heavily inspired by F#","archived":false,"fork":false,"pushed_at":"2023-11-01T18:35:59.000Z","size":145,"stargazers_count":31,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-12T00:04:50.287Z","etag":null,"topics":["fsharp","functional","maybe","option","railway","result","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nozzlegear.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-28T20:05:18.000Z","updated_at":"2025-04-11T14:06:23.000Z","dependencies_parsed_at":"2024-06-19T06:11:37.505Z","dependency_job_id":"69ab0ab1-fff3-47e2-a86d-914ca4a78658","html_url":"https://github.com/nozzlegear/railway","commit_stats":{"total_commits":53,"total_committers":1,"mean_commits":53.0,"dds":0.0,"last_synced_commit":"4dc2a4c1d4b04a5e442591cd0c53ca939b515d6b"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nozzlegear%2Frailway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nozzlegear%2Frailway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nozzlegear%2Frailway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nozzlegear%2Frailway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nozzlegear","download_url":"https://codeload.github.com/nozzlegear/railway/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248832897,"owners_count":21168784,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fsharp","functional","maybe","option","railway","result","typescript"],"created_at":"2024-11-16T00:12:15.705Z","updated_at":"2025-04-14T06:30:38.775Z","avatar_url":"https://github.com/nozzlegear.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @nozzlegear/railway\n\nThis is a collection of functional helpers and monads, heavily inspired by F#, to help facilitate \"railway-oriented\" programming in TypeScript.\n\n## Installation\n\n```sh\nyarn install @nozzlegear/railway\n```\n\n```ts\nimport { compute, pipe, Result, Async } from \"@nozzlegear/railway\";\n```\n\n## Usage\n\n### `compute`\n\nThe `compute` function is an incredibly simple wrapper whose purpose is to group up temporary variables into a \"computation block\" (think a `let` binding in F#), preventing them from polluting your function scope. It also encourages immutability (but does not require it).\n\n```ts\nconst order = await compute(async () =\u003e {\n    const displayId = await database.getDisplayIdAsync();\n    const order = {\n        ...baseOrder,\n        user_id: req.user_id,\n        display_id: displayId\n    }\n    const result = await database.createAsync(order);\n\n    return {\n        ...order,\n        _id: result.id,\n        _rev: result.rev\n    }\n})\n```\n\nIn this example, order is only declared once, and the temporary variables that only it uses (displayId, result) are wrapped up neatly in the computation. Here's the same example without wrapping in a computation:\n\n```ts\nconst displayId = await database.getDisplayIdAsync();\nlet order = {\n    ...baseOrder,\n    user_id: req.user_id,\n    display_id: displayId\n}\nconst result = await database.createAsync(order);\norder = {\n    ...order,\n    _id: result.id,\n    _rev: result.rev\n}\n```\n\nIn this example, order is mutable by default (because its value needs to be reassigned after getting the `result`), and the temporary variables that only it uses are polluting the scope of the rest of the function.\n\n### `pipe`\n\nThe `pipe` function is a simple function chain that pipes the result of the last function to the next function. Since custom operators are impossible, the `pipe` function instead uses `.chain` and `.value`.\n\nNote that the functions are executed as they're chained, not when the value is retrieved.\n\n```ts\nfunction getName() {\n    return \"joshua\"\n}\n\nfunction capitalize(str: string) {\n    const first = str[0];\n    const rest = str.slice(1);\n\n    return first.toUpperCase() + rest.toLowerCase();\n}\n\nfunction formatMessage(name: string) {\n    return `Hello, ${name}!`\n}\n\n// Pipe the functions together to create the message\nconst message = pipe(getName())\n    .chain(capitalize)\n    .chain(formatMessage)\n    .value();\n\nconsole.log(message) // \"Hello, Joshua!\"\n```\n\n### `Result\u003cT\u003e`\n\nThe `Result` monad encourages railway-oriented programming by wrapping values in either an \"Ok\" value or an \"Error\" value. You can safely operate on the monad without worrying about whether the value is an error or not, as the functions will only operate on \"Ok\" values.\n\n```ts\nconst result = Result.ofValue\u003cstring\u003e(\"5\")\n    .map(str =\u003e parseInt(str));\n\nif (result.isOk()) {\n    console.log(result.getValue()) // 5\n} else {\n    console.error(result.getError())\n}\n```\n\nWhen the value is an error, none of the functions will run:\n\n```ts\nconst result = Result.ofError\u003cstring\u003e(new Error(\"Test error\"))\n    .map(str =\u003e parseInt(str));\n\nif (result.isOk()) {\n    console.log(result.getValue()) // will not be called\n} else {\n    console.error(result.getError()) // Error with message \"Test error\"\n}\n```\n\n**Always check that a Result has a value before getting the value, and check that a Result has an error before getting the error**. Attemping to do either of these operations without first checking can throw an error and break your program:\n\n```ts\nconst firstResult = Result.ofError(new Error(\"Test error\"));\n\nfirstResult.getValue() // This WILL throw an error\n\nconst secondResult = Result.ofValue(5)\n\nsecondResult.getError() // This WILL throw an error\n```\n\nYou can set a default value for the Result, which will be used if the value is an error:\n\n```ts\nconst value = Result.ofError\u003cstring\u003e(new Error(\"Test error\"))\n    .map(str =\u003e parseInt(str))\n    .defaultValue(10)\n\nconsole.log(value) // 10\n```\n\nThe `Result` monad's functions can also be curried, which is ideal for working with the `pipe` function:\n\n```ts\nfunction getName() {\n    return \"joshua\"\n}\n\nfunction capitalize(str: string) {\n    const first = str[0];\n    const rest = str.slice(1);\n\n    return first.toUpperCase() + rest.toLowerCase();\n}\n\nfunction formatMessage(name: string) {\n    return `Hello, ${name}!`\n}\n\n// Pipe the functions together to create the message, using the Result monad's currying\nconst message = pipe(getName())\n    .chain(Result.ofValue)\n    .chain(Result.map(capitalize))\n    .chain(Result.map(formatMessage))\n    .chain(Result.defaultValue(\"Hello, Newman...\"))\n    .value();\n\nconsole.log(message) // \"Hello, Joshua!\"\n```\n\nYou can wrap functions and promises in a Result, which will internally wrap them in a try/catch (or add a `.catch` to the promise chain):\n\n```ts\nconst fnResult = Result.ofFunction(() =\u003e {\n    return somethingUndefined / 0\n})\n\nconsole.log(fnResult.isError()) // true\n\nif (fnResult.isError()) {\n    console.error(fnResult.getError()) // ReferenceError: somethingUndefined is not defined at ....\n}\n\n// Be sure to await the result\nconst promResult = await Result.ofPromise(async () =\u003e {\n    return 5\n})\n\nconsole.log(promResult.isOk()) // true\n\nif (promResult.isOk()) {\n    console.log(promResult.getValue()) // 5\n}\n```\n\n### `Async\u003cT\u003e`\n\nThe Async monad wraps a promise and adds a couple of functions that make it easier to work with the value of the promise. The biggest change is that `Async.map` takes the value of the promise and returns the exact value you return, where the native `Promise.map` would return an array of the value you return.\n\n```ts\nasync function getSomethingAsync() {\n    return \"5\";\n}\n\nconst value = await Async.ofPromise(getSomethingAsync())\n    .map(parseInt)\n    .get() // Must call .get() to get the final promise so you can await it\n\nconsole.log(value) // 5\n\n// Doing the same thing with a promise would return an array:\nconst secondValue = await getSomethingAsync().map(parseInt)\n\nconsole.log(secondValue) // [5]\n```\n\nYou can also bind promises, merging the promise returned by the function into the value of the Async's promise:\n\n```ts\nasync function getSomethingAsync() {\n    return \"5\"\n}\n\nasync function parseIntAsync(str: string) {\n    return parseInt(str);\n}\n\n// This is bad, it returns a Promise\u003cint\u003e even after being awaited\nconst mappedValue: Promise\u003cnumber\u003e = await Async.ofPromise(getSomethingAsync())\n    .map(parseIntAsync)\n    .get()\n\n// This is good, it binds the promise returned from `parseIntAsync`\nconst value: number = await Async.ofPromise(getSomethingAsync())\n    .bind(parseIntAsync) \n    .get()\n\nconsole.log(value) // 5\n```\n\nJust like the `Result` monad, `Async` also has curried functions:\n\n```ts\nasync function getName() {\n    return \"joshua\"\n}\n\nfunction capitalize(str: string) {\n    const first = str[0];\n    const rest = str.slice(1);\n\n    return first.toUpperCase() + rest.toLowerCase();\n}\n\nasync function formatMessage(name: string) {\n    return `Hello, ${name}!`\n}\n\n// Pipe the functions together to create the message, using the Async monad's currying\nconst message = await pipe(getName())\n    .chain(Async.ofPromise)\n    .chain(Async.map(capitalize))\n    .chain(Async.bind(formatMessage))\n    .chain(Async.get)\n    .value();\n\nconsole.log(message) // \"Hello, Joshua!\"\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnozzlegear%2Frailway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnozzlegear%2Frailway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnozzlegear%2Frailway/lists"}