{"id":13567035,"url":"https://github.com/patrickmichalina/typescript-monads","last_synced_at":"2025-03-31T11:00:22.153Z","repository":{"id":45889205,"uuid":"143943338","full_name":"patrickmichalina/typescript-monads","owner":"patrickmichalina","description":"📚Write safer TypeScript using Maybe, List, Result, and Either monads.","archived":false,"fork":false,"pushed_at":"2025-03-24T03:11:38.000Z","size":1885,"stargazers_count":120,"open_issues_count":5,"forks_count":12,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-24T03:28:27.258Z","etag":null,"topics":["either-monad","functional-programming","immutability","javascript","list-monad","maybe-monad","monads","node","reader-monad","result-monad","typescript"],"latest_commit_sha":null,"homepage":"","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/patrickmichalina.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-08-08T01:06:34.000Z","updated_at":"2025-03-24T03:05:52.000Z","dependencies_parsed_at":"2024-01-09T09:47:07.463Z","dependency_job_id":"c1cf43c7-9b36-4860-9c16-d7f448e957a4","html_url":"https://github.com/patrickmichalina/typescript-monads","commit_stats":{"total_commits":155,"total_committers":6,"mean_commits":"25.833333333333332","dds":"0.18709677419354842","last_synced_commit":"bd78b8049fd210d107f78c4f747b62dcafd6d156"},"previous_names":[],"tags_count":81,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickmichalina%2Ftypescript-monads","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickmichalina%2Ftypescript-monads/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickmichalina%2Ftypescript-monads/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickmichalina%2Ftypescript-monads/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patrickmichalina","download_url":"https://codeload.github.com/patrickmichalina/typescript-monads/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246457967,"owners_count":20780676,"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":["either-monad","functional-programming","immutability","javascript","list-monad","maybe-monad","monads","node","reader-monad","result-monad","typescript"],"created_at":"2024-08-01T13:02:22.102Z","updated_at":"2025-03-31T11:00:22.129Z","avatar_url":"https://github.com/patrickmichalina.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003ch1 align=\"center\" style=\"border-bottom: none;\"\u003e📚 typescript-monads\u003c/h1\u003e\n\u003ch3 align=\"center\"\u003eBetter TypeScript Control Flow\u003c/h3\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://circleci.com/gh/patrickmichalina/typescript-monads\"\u003e\n    \u003cimg alt=\"circeci\" src=\"https://circleci.com/gh/patrickmichalina/typescript-monads.svg?style=shield\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/semantic-release/semantic-release\"\u003e\n    \u003cimg alt=\"semantic-release\" src=\"https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/typescript-monads\"\u003e\n    \u003cimg alt=\"npm latest version\" src=\"https://img.shields.io/npm/v/typescript-monads/latest.svg\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n**typescript-monads** helps you write safer code by using abstractions over messy control flow and state.\n\n# Installation\nYou can use this library in the browser, node, or a bundler\n\n## Node or as a module\n```bash\nnpm install typescript-monads\n```\n\n## Browser\n```html\n\u003chead\u003e\n \u003cscript src=\"https://unpkg.com/typescript-monads\"\u003e\u003c/script\u003e\n \u003c!-- or use a specific version to avoid a http redirect --\u003e \n \u003cscript src=\"https://unpkg.com/typescript-monads@5.3.0/index.min.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n```\n\n```js\nvar someRemoteValue;\ntypescriptMonads.maybe(someRemoteValue).tapSome(console.log)\n```\n\n# Example Usage\n\n* [Maybe](#maybe)\n* [List](#list)\n* [Either](#either)\n* [Reader](#reader)\n* [Result](#result)\n* [State](#state)\n* [Logger](#logger)\n\n# Maybe\n\nThe `Maybe` monad represents values that may or may not exist. It's a safe way to handle potentially null or undefined values without resorting to null checks throughout your code.\n\n```typescript\nimport { maybe, none } from 'typescript-monads'\n\n// Creating Maybe instances\nconst someValue = maybe(42)        // Maybe with a value\nconst noValue = maybe(null)        // Maybe with no value (None)\nconst alsoNoValue = none\u003cnumber\u003e() // Explicitly create a None\n\n// Safe value access\nsomeValue.valueOr(0)           // 42\nnoValue.valueOr(0)             // 0\nsomeValue.valueOrCompute(() =\u003e expensiveCalculation()) // 42 (computation skipped)\nnoValue.valueOrCompute(() =\u003e expensiveCalculation())   // result of computation\n\n// Conditional execution with pattern matching\nsomeValue.match({\n  some: val =\u003e console.log(`Got a value: ${val}`),\n  none: () =\u003e console.log('No value present')\n}) // logs: \"Got a value: 42\"\n\n// Side effects with tap\nsomeValue.tap({\n  some: val =\u003e console.log(`Got ${val}`),\n  none: () =\u003e console.log('Nothing to see')\n})\n\n// Conditional side effects\nsomeValue.tapSome(val =\u003e console.log(`Got ${val}`))\nnoValue.tapNone(() =\u003e console.log('Nothing here'))\n\n// Chaining operations (only executed for Some values)\nmaybe(5)\n  .map(n =\u003e n * 2)           // maybe(10)\n  .filter(n =\u003e n \u003e 5)        // maybe(10)\n  .flatMap(n =\u003e maybe(n + 1)) // maybe(11)\n\n// Transforming to other types\nmaybe(5).toResult('No value found') // Ok(5)\nmaybe(null).toResult('No value found') // Fail('No value found')\n\n// Working with RxJS (with rxjs optional dependency)\nimport { maybeToObservable } from 'typescript-monads'\nmaybeToObservable(maybe(5)) // Observable that emits 5 and completes\nmaybeToObservable(none())   // Observable that completes without emitting\n```\n\n## List\n\nThe `List` monad is a lazily evaluated collection with chainable operations. It provides many of the common list processing operations found in functional programming languages.\n\n```typescript\nimport { List } from 'typescript-monads'\n\n// Creating Lists\nconst fromValues = List.of(1, 2, 3, 4, 5)\nconst fromIterable = List.from([1, 2, 3, 4, 5])\nconst numbersFromRange = List.range(1, 10)    // 1 to 10\nconst infiniteNumbers = List.integers()        // All integers (use with take!)\nconst empty = List.empty\u003cnumber\u003e()\n\n// Basic operations\nfromValues.toArray() // [1, 2, 3, 4, 5]\nfromValues.headOrUndefined() // 1\nfromValues.headOr(0) // 1\nempty.headOr(0) // 0\n\n// Transformations\nfromValues\n  .map(n =\u003e n * 2)                 // [2, 4, 6, 8, 10]\n  .filter(n =\u003e n \u003e 5)              // [6, 8, 10]\n  .take(2)                         // [6, 8]\n  .drop(1)                         // [8]\n\n// LINQ-style operations\nfromValues.sum()                    // 15\nfromValues.all(n =\u003e n \u003e 0)          // true\nfromValues.any(n =\u003e n % 2 === 0)    // true\nfromValues.where(n =\u003e n % 2 === 0)  // [2, 4]\n\n// Conversion\nfromValues.toDictionary()           // { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }\nconst users = List.of(\n  { id: 'a', name: 'Alice' },\n  { id: 'b', name: 'Bob' }\n)\nusers.toDictionary('id') // { 'a': { id: 'a', name: 'Alice' }, 'b': { id: 'b', name: 'Bob' } }\n```\n\n## Either\n\nThe `Either` monad represents values that can be one of two possible types. It's often used to represent a value that can be either a success (Right) or a failure (Left).\n\n```typescript\nimport { either } from 'typescript-monads'\n\n// Creating Either instances\nconst rightValue = either\u003cstring, number\u003e(undefined, 42) // Right value\nconst leftValue = either\u003cstring, number\u003e('error', undefined) // Left value\n\n// Checking which side is present\nrightValue.isRight() // true\nrightValue.isLeft()  // false\nleftValue.isRight()  // false \nleftValue.isLeft()   // true\n\n// Pattern matching\nrightValue.match({\n  right: val =\u003e `Success: ${val}`,\n  left: err =\u003e `Error: ${err}`\n}) // \"Success: 42\"\n\n// Side effects with tap\nrightValue.tap({\n  right: val =\u003e console.log(`Got right: ${val}`),\n  left: err =\u003e console.log(`Got left: ${err}`)\n})\n\n// Transformation (only applies to Right values)\nrightValue.map(n =\u003e n * 2) // Either with Right(84)\nleftValue.map(n =\u003e n * 2)  // Either with Left('error') unchanged\n\n// Chaining (flatMap only applies to Right values)\nrightValue.flatMap(n =\u003e either(undefined, n + 10)) // Either with Right(52)\nleftValue.flatMap(n =\u003e either(undefined, n + 10))  // Either with Left('error') unchanged\n```\n\n## Reader\n\nThe `Reader` monad represents a computation that depends on some external configuration or environment. It's useful for dependency injection.\n\n```typescript\nimport { reader } from 'typescript-monads'\n\n// Define a configuration type\ninterface Config {\n  apiUrl: string\n  apiKey: string\n}\n\n// Create readers that depend on this configuration\nconst getApiUrl = reader\u003cConfig, string\u003e(config =\u003e config.apiUrl)\nconst getApiKey = reader\u003cConfig, string\u003e(config =\u003e config.apiKey)\n\n// Compose readers to build more complex operations\nconst getAuthHeader = getApiKey.map(key =\u003e `Bearer ${key}`)\n\n// Create a reader for making an API request\nconst fetchData = reader\u003cConfig, Promise\u003cResponse\u003e\u003e(config =\u003e {\n  return fetch(`${config.apiUrl}/data`, {\n    headers: {\n      'Authorization': `Bearer ${config.apiKey}`\n    }\n  })\n})\n\n// Execute the reader with a specific configuration\nconst config: Config = {\n  apiUrl: 'https://api.example.com',\n  apiKey: 'secret-key-123'\n}\n\nconst apiUrl = getApiUrl.run(config)  // 'https://api.example.com'\nconst authHeader = getAuthHeader.run(config) // 'Bearer secret-key-123'\nfetchData.run(config).then(response =\u003e {\n  // Handle API response\n})\n```\n\n## Result\n\nThe `Result` monad represents operations that can either succeed with a value or fail with an error. It's similar to Either but with more specific semantics for success/failure.\n\n```typescript\nimport { ok, fail, catchResult } from 'typescript-monads'\n\n// Creating Result instances\nconst success = ok\u003cnumber, string\u003e(42)        // Success with value 42\nconst failure = fail\u003cnumber, string\u003e('error') // Failure with error 'error'\n\n// Safely catching exceptions\nconst result = catchResult\u003cnumber, Error\u003e(\n  () =\u003e {\n    // Code that might throw\n    if (Math.random() \u003e 0.5) {\n      throw new Error('Failed')\n    }\n    return 42\n  }\n)\n\n// Checking result type\nsuccess.isOk()   // true\nsuccess.isFail() // false\nfailure.isOk()   // false\nfailure.isFail() // true\n\n// Extracting values safely\nsuccess.unwrapOr(0)   // 42\nfailure.unwrapOr(0)   // 0\nsuccess.unwrap()      // 42\n// failure.unwrap()   // Throws error\n\n// Convert to Maybe\nsuccess.maybeOk()    // Maybe with Some(42)\nfailure.maybeOk()    // Maybe with None\nsuccess.maybeFail()  // Maybe with None\nfailure.maybeFail()  // Maybe with Some('error')\n\n// Pattern matching\nsuccess.match({\n  ok: val =\u003e `Success: ${val}`,\n  fail: err =\u003e `Error: ${err}`\n}) // \"Success: 42\"\n\n// Transformations\nsuccess.map(n =\u003e n * 2)          // Ok(84)\nfailure.map(n =\u003e n * 2)          // Fail('error') unchanged\nfailure.mapFail(e =\u003e `${e}!`)    // Fail('error!') \n\n// Chaining\nsuccess.flatMap(n =\u003e ok(n + 10)) // Ok(52)\nfailure.flatMap(n =\u003e ok(n + 10)) // Fail('error') unchanged\n\n// Side effects\nsuccess.tap({\n  ok: val =\u003e console.log(`Success: ${val}`),\n  fail: err =\u003e console.log(`Error: ${err}`)\n})\nsuccess.tapOk(val =\u003e console.log(`Success: ${val}`))\nfailure.tapFail(err =\u003e console.log(`Error: ${err}`))\n\n// Chaining with side effects\nsuccess\n  .tapOkThru(val =\u003e console.log(`Success: ${val}`))\n  .map(n =\u003e n * 2)\n\n// Converting to promises\nimport { resultToPromise } from 'typescript-monads'\nresultToPromise(success) // Promise that resolves to 42\nresultToPromise(failure) // Promise that rejects with 'error'\n```\n\n## State\n\nThe `State` monad represents computations that can read and transform state. It's useful for threading state through a series of operations.\n\n```typescript\nimport { state } from 'typescript-monads'\n\n// Define a state type\ninterface AppState {\n  count: number\n  name: string\n}\n\n// Initial state\nconst initialState: AppState = {\n  count: 0,\n  name: ''\n}\n\n// Create operations that work with the state\nconst incrementCount = state\u003cAppState, number\u003e(s =\u003e \n  [{ ...s, count: s.count + 1 }, s.count + 1]\n)\n\nconst setName = (name: string) =\u003e state\u003cAppState, void\u003e(s =\u003e \n  [{ ...s, name }, undefined]\n)\n\nconst getCount = state\u003cAppState, number\u003e(s =\u003e [s, s.count])\n\n// Compose operations\nconst operation = incrementCount\n  .flatMap(() =\u003e setName('Alice'))\n  .flatMap(() =\u003e getCount)\n\n// Run the state operation\nconst result = operation.run(initialState)\nconsole.log(result.state)  // { count: 1, name: 'Alice' }\nconsole.log(result.value)  // 1\n```\n\n## Logger\n\nThe `Logger` monad lets you collect logs during a computation. It's useful for debugging or creating audit trails.\n\n```typescript\nimport { logger, tell } from 'typescript-monads'\n\n// Create a logger with initial logs and value\nconst initialLogger = logger\u003cstring, number\u003e(['Starting process'], 0)\n\n// Add logs and transform value\nconst result = initialLogger\n  .flatMap(val =\u003e {\n    return logger(['Incrementing value'], val + 1)\n  })\n  .flatMap(val =\u003e {\n    return logger(['Doubling value'], val * 2)\n  })\n\n// Extract all logs and final value\nresult.runUsing(({ logs, value }) =\u003e {\n  console.log('Logs:', logs)    // ['Starting process', 'Incrementing value', 'Doubling value']\n  console.log('Final value:', value) // 2\n})\n\n// Start with a single log entry\nconst startLogger = tell\u003cstring\u003e('Beginning')\n\n// Add a value to the logger\nconst withValue = logger.startWith\u003cstring, number\u003e('Starting with value:', 42)\n\n// Extract results using pattern matching\nconst output = result.runUsing(({ logs, value }) =\u003e {\n  return { \n    history: logs.join('\\n'),\n    result: value \n  }\n})\n```\n\n# Integration with Other Libraries\n\n## RxJS Integration\n\nThis library offers RxJS integration with the `Maybe` and `Result` monads:\n\n```typescript\nimport { maybeToObservable } from 'typescript-monads'\nimport { resultToObservable } from 'typescript-monads'\nimport { of } from 'rxjs'\nimport { flatMap } from 'rxjs/operators'\n\n// Convert Maybe to Observable\nof(maybe(5)).pipe(\n  flatMap(maybeToObservable)\n).subscribe(val =\u003e console.log(val)) // logs 5 and completes\n\n// Convert Result to Observable  \nof(ok(42)).pipe(\n  flatMap(resultToObservable)\n).subscribe(\n  val =\u003e console.log(`Success: ${val}`),\n  err =\u003e console.error(`Error: ${err}`)\n)\n```\n\n## Promise Integration\n\nYou can convert `Result` monads to promises:\n\n```typescript\nimport { resultToPromise } from 'typescript-monads'\n\n// Convert Result to Promise\nresultToPromise(ok(42))\n  .then(val =\u003e console.log(`Success: ${val}`))\n  .catch(err =\u003e console.error(`Error: ${err}`))\n\n// Catch exceptions and convert to Result\nasync function fetchData() {\n  try {\n    const response = await fetch('https://api.example.com/data')\n    if (!response.ok) {\n      throw new Error(`HTTP error ${response.status}`)\n    }\n    return ok(await response.json())\n  } catch (error) {\n    return fail(error)\n  }\n}\n```\n\n# Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n# License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrickmichalina%2Ftypescript-monads","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatrickmichalina%2Ftypescript-monads","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrickmichalina%2Ftypescript-monads/lists"}