{"id":16694501,"url":"https://github.com/foxfriends/raiser","last_synced_at":"2025-03-13T23:43:26.479Z","repository":{"id":83353676,"uuid":"371579769","full_name":"foxfriends/raiser","owner":"foxfriends","description":"Raise multiple errors without throwing","archived":false,"fork":false,"pushed_at":"2024-06-17T13:58:14.000Z","size":77,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-20T19:13:25.268Z","etag":null,"topics":["error-handling","fantasy-land","functional-programming","monad"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/foxfriends.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2021-05-28T04:31:26.000Z","updated_at":"2024-06-17T13:58:14.000Z","dependencies_parsed_at":"2023-03-05T00:15:51.151Z","dependency_job_id":null,"html_url":"https://github.com/foxfriends/raiser","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxfriends%2Fraiser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxfriends%2Fraiser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxfriends%2Fraiser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxfriends%2Fraiser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/foxfriends","download_url":"https://codeload.github.com/foxfriends/raiser/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243500771,"owners_count":20300770,"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":["error-handling","fantasy-land","functional-programming","monad"],"created_at":"2024-10-12T16:46:14.176Z","updated_at":"2025-03-13T23:43:26.456Z","avatar_url":"https://github.com/foxfriends.png","language":"JavaScript","readme":"# Raiser\n\nRaise multiple errors without throwing.\n\nThis is a [Fantasy Land][] compliant monad (maybe) implementing a way to collect multiple errors\nwithout aborting the computation. I think this is a real monad, but I have not checked the laws,\nso maybe it's actually just something pretending to be a monad. Trust at your own discretion.\n\n[Fantasy Land]: https://github.com/fantasyland/fantasy-land\n\nAnyway, whether or not it's a good monad, this may be a useful construct.\n\n# API\n\n\u003e Note: the examples below assume this is published to NPM in a package called `raiser`. It is not.\n\u003e If you actually want to use this library let me know and I'll publish it, but under some other\n\u003e name because `raiser` is taken and honestly not a great name but it's just what I came up with\n\u003e when I was typing it out.\n\n## `Raiser :: TypeRep (Raiser e a)`\n\n```javascript\nimport Raiser from 'raiser';\n```\n\nThis is the core of this whole thing, the Type Representative of the `Raiser` monad. This is\nreally not all that special if you are familiar with the `State` monad, and can be understood\nas a specialization like this: `Raiser e a = State [e] a`.\n\n### `Raiser :: ([e] -\u003e { errors :: [e], value :: a }) -\u003e Raiser e a`\n\nThis is the constructor (e.g. `new Raiser`). Not really recommended for use, the underlying\nrepresentation of a `Raiser` is a function that:\n1.  takes a single parameter `errors :: [e]`, the list of errors that have been\n    raised in the current context; and\n2.  returns a record of the form `{ errors, value }` where `errors :: [e]` is the list of\n    errors with any newly raised ones, and `value :: a` is the resulting value of this\n    computation.\n\nBy using the operators and do-notation helper as provided, you should be able to avoid this\nconstructor easily.\n\n### `Raiser#['fantasy-land/of'] :: a -\u003e Raiser e a`\n\nProduces a `Raiser` that computes the constant value provided, without raising an errors.\n\n### `Raiser#['fantasy-land/map'] :: Raiser e a ~\u003e (a -\u003e b) -\u003e Raiser e b`\n\nMaps the value of the `Raiser`, leaving the errors untouched.\n\n### `Raiser#['fantasy-land/ap'] :: Raiser e a ~\u003e Raiser e (a -\u003e b) -\u003e Raiser e b`\n\nApplies the function which is contained in another `Raiser` to the value contained in this\n`Raiser`. The errors raised by both will be included in the result.\n\n### `Raiser#['fantasy-land/chain'] :: Raiser e a ~\u003e (a -\u003e Raiser e b) -\u003e Raiser e b`\n\nChains the value of the Raiser, transforming the value while also allowing for new errors\nto be raised.\n\n### `Raiser#computation :: [e] -\u003e { errors :: [e], value :: a }`\n\nRuns a Raiser by calling the underlying function representation that was explained\nwith the constructor. As with the constructor, it's not likely you'll want to use this\ndirectly.\n\n## `Errors`\n\n```javascript\nimport { Errors } from 'raiser';\n```\n\nWhen a Raiser decides to abort its computation, it will do so by throwing an error of type\n`Errors`. This value has a property `errors` which is an array containing all the values\nraised to the `Raiser` that is aborting.\n\n## Consuming `Raisers`\n\nI would not be surprised if 100% of the time you are dealing with Raisers, you create\nthem using `doRaiser` and consume them with one of the three functions `runRaiser`,\n`evaluateRaiser`, or `tryRaiser`. Though you could use the raw class as described above,\nit's really nasty and not a good time.\n\n### `doRaiser :: Generator (forall b. Raiser e b) a -\u003e Raiser e a`\n\nBuilds a `Raiser` from a generator in which `yield` takes the role of `\u003c-`, allowing you\nto yield other `Raisers` and collect their errors naturally. This is fairly similar to\nsome other Fantasy Land do-notation things you may see elsewhere, but is included here\nto spare you having to find another. Hopefully the examples below show you how this works.\n\n\u003e `doRaiser` is actually a specialization of `do_ :: Monad m =\u003e TypeRep m -\u003e Generator (forall b. m b) a -\u003e m a`,\n\u003e which you are also welcome to use on any other Fantasy Land compliant monad.\n\nSome things to keep in mind, as Javascript is not that conducive to the do-notation:\n1.  A raiser created this way is single use. You can't run it again.\n2.  `doRaiser` (and by extension, `do_`) will eagerly evaluate up to the first yield.\n    The rest won't be evaluated until you run it with `runRaiser` or whatever. Not a\n    great behaviour I think, but it will do.\n3.  `do_` will not work with non-deterministic monads (e.g. list). See [Burrido][] if\n    you need such a thing, but that's not always great either.\n\n[Burrido]: https://github.com/pelotom/burrido\n\n### `runRaiser :: Raiser e a -\u003e { errors :: [e], value :: a }`\n\nRuns a `Raiser`, returning both the list of raised errors and the resulting value.\n\n```javascript\nimport { doRaiser, raise, runRaiser } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  yield raise(new Error('bad'));\n  return 'ok';\n});\n\nrunRaiser(raiser); // { errors: [Error('bad')], value: 'ok' }\n```\n\n### `evaluateRaiser :: Raiser e a -\u003e { errors :: [e], value :: a }`\n\nRuns a `Raiser`, ignoring the raised errors and just returning the resulting value.\n\n```javascript\nimport { doRaiser, raise, evaluateRaiser } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  yield raise(new Error('bad'));\n  return 'ok';\n});\n\nevaluateRaiser(raiser); // 'ok'\n```\n\n### `tryRaiser :: Raiser e a -\u003e { errors :: [e], value :: a }`\n\nRuns a `Raiser`, aborting *after* completing the computation if any errors were\nraised in the process. Note that this aborts *after* the computation, so the\nreturn value of the `Raiser` will be evaluated anyway, and then discarded.\n\n```javascript\nimport { doRaiser, raise, tryRaiser } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  yield raise(new Error('bad'));\n  return 'ok';\n});\n\ntryRaiser(raiser); // !throws\n```\n\n## Working with `Raisers`\n\n### `raise :: e -\u003e Raiser e ()`\n\nRaises an error into the Raiser.\n\n```javascript\nimport { doRaiser, raise, tryRaiser } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  yield raise(new Error('bad'));\n  yield raise(new Error('worse'));\n  return 'ok';\n});\n\ntryRaiser(raiser); // !throws\n```\n\n### `lower :: e -\u003e Raiser e ()`\n\nThe opposite of `raise`, this will un-raise a previously raised error. If such an error was\nnot previously raised, nothing will happen.\n\n```javascript\nimport { doRaiser, raise, lower, tryRaiser } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  const error = new Error;\n  yield raise(error);\n  yield lower(error);\n  return 'ok';\n});\n\ntryRaiser(raiser); // 'ok'\n```\n\n### `inspect :: Raiser e [e]`\n\nGets the current list of errors. This does not modify that list, just lets you access it,\nmaybe so you can look for ones you know how to handle and `lower` them.\n\n```javascript\nimport { doRaiser, raise, inspect, runRaiser } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  yield raise('ok');\n  const errors = yield inspect;\n  return errors[0];\n});\n\nrunRaiser(raiser); // { errors: ['ok'], value: 'ok' }\n```\n\n### `consume :: Raiser e [e]`\n\nGets the current list of errors, and dismisses them all.\n\n```javascript\nimport { doRaiser, raise, consume, runRaise } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  yield raise('ok');\n  const errors = yield consume;\n  return errors[0];\n});\n\nrunRaiser(raiser); // { errors: [], value: 'ok' }\n```\n\n### `checkpoint :: Raiser e ()`\n\nDefines a checkpoint at which, if there are any errors that were previously raised, the computation\nis aborted immediately (by throwing an `Errors` containing everything that was raised).\n\nNotice that, unless handled along the way, this can cause `runRaiser` or `evaluateRaiser` to throw,\ndespite their usual not throwing of unchecked raised errors.\n\n```javascript\nimport { doRaiser, raise, consume, evaulateRaiser } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  yield raise('error');\n  yield checkpoint;\n  console.log('This will not happen');\n});\n\nrunRaiser(raiser); // !throws\n```\n\n### `tryOr :: a -\u003e Raiser e a -\u003e Raiser e a`\n\nAttempts to run another `Raiser`, and if that one aborts (due to using `checkpoint`), the abort is\nstopped and the errors are just put back into the raised state. Since in the case of an abort, there\nis no return value available, you must provide a fallback as the first parameter.\n\n```javascript\nimport { doRaiser, raise, checkpoint, tryOr, evaluateRaiser } from 'raiser';\n\nconst raiser = doRaiser(function* () {\n  yield raise('error');\n  const value = yield tryOr('Sorry', doRaiser(function* () {\n    yield raise('another error');\n    yield checkpoint;\n    return 'Success!';\n  }));\n  return value;\n});\n\nrunRaiser(raiser); // { errors: ['error', 'another error'], value: 'Sorry' }\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxfriends%2Fraiser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoxfriends%2Fraiser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxfriends%2Fraiser/lists"}