{"id":19972582,"url":"https://github.com/jpb06/effect-errors","last_synced_at":"2025-05-04T01:30:52.815Z","repository":{"id":226111489,"uuid":"767767470","full_name":"jpb06/effect-errors","owner":"jpb06","description":"Fancy errors reporting for effect","archived":false,"fork":false,"pushed_at":"2025-05-01T15:51:19.000Z","size":5986,"stargazers_count":10,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-03T16:37:40.097Z","etag":null,"topics":["effect-ts","error-reporting","poc"],"latest_commit_sha":null,"homepage":"https://remix-effect-errors.vercel.app/","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/jpb06.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"zenodo":null}},"created_at":"2024-03-05T21:34:21.000Z","updated_at":"2025-05-01T15:49:45.000Z","dependencies_parsed_at":"2024-05-20T11:47:53.055Z","dependency_job_id":"05dfe569-0e2f-4fcf-a6cf-a7b8db14e6bf","html_url":"https://github.com/jpb06/effect-errors","commit_stats":{"total_commits":197,"total_committers":2,"mean_commits":98.5,"dds":"0.050761421319796995","last_synced_commit":"89d30049d0b4fb7df713677819aa0b800a08d114"},"previous_names":["jpb06/effect-errors"],"tags_count":192,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-errors","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-errors/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-errors/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpb06%2Feffect-errors/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jpb06","download_url":"https://codeload.github.com/jpb06/effect-errors/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252276955,"owners_count":21722447,"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":["effect-ts","error-reporting","poc"],"created_at":"2024-11-13T03:08:36.799Z","updated_at":"2025-05-04T01:30:52.799Z","avatar_url":"https://github.com/jpb06.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# effect-errors\n\n[![Open in Visual Studio Code](https://img.shields.io/static/v1?logo=visualstudiocode\u0026label=\u0026message=Open%20in%20Visual%20Studio%20Code\u0026labelColor=2c2c32\u0026color=007acc\u0026logoColor=007acc)](https://github.dev/jpb06/effect-errors)\n![Last commit](https://img.shields.io/github/last-commit/jpb06/effect-errors?logo=git)\n![npm downloads](https://img.shields.io/npm/dw/effect-errors?logo=npm\u0026logoColor=red\u0026label=npm%20downloads)\n![npm bundle size](https://img.shields.io/bundlephobia/min/effect-errors)\n![Github workflow](https://img.shields.io/github/actions/workflow/status/jpb06/effect-errors/ci.yml?branch=main\u0026logo=github-actions\u0026label=last%20workflow)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jpb06_effect-errors)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=sqale_rating)](https://sonarcloud.io/dashboard?id=jpb06_effect-errors)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=jpb06_effect-errors)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=reliability_rating)](https://sonarcloud.io/dashboard?id=jpb06_effect-errors)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=jpb06_effect-errors)\n[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=ncloc)](https://sonarcloud.io/summary/new_code?id=jpb06_effect-errors)\n[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=jpb06_effect-errors)\n[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=code_smells)](https://sonarcloud.io/dashboard?id=jpb06_effect-errors)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=bugs)](https://sonarcloud.io/summary/new_code?id=jpb06_effect-errors)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=jpb06_effect-errors)\n[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=jpb06_effect-errors\u0026metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=jpb06_effect-errors)\n\nSome sort of POC to improve the way [Effect](https://effect.website/) reports errors in a dev env 🤔\n\n\u003c!-- readme-package-icons start --\u003e\n\n\u003cp align=\"left\"\u003e\u003ca href=\"https://docs.github.com/en/actions\" target=\"_blank\"\u003e\u003cimg height=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/GithubActions-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://www.typescriptlang.org/docs/\" target=\"_blank\"\u003e\u003cimg height=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/TypeScript.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://nodejs.org/en/docs/\" target=\"_blank\"\u003e\u003cimg height=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/NodeJS-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://pnpm.io/motivation\" target=\"_blank\"\u003e\u003cimg height=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Pnpm-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://biomejs.dev/guides/getting-started/\" target=\"_blank\"\u003e\u003cimg height=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Biome-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://esbuild.github.io/getting-started/#install-esbuild\" target=\"_blank\"\u003e\u003cimg height=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Esbuild-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://vitest.dev/guide/\" target=\"_blank\"\u003e\u003cimg height=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Vitest-Dark.svg\" /\u003e\u003c/a\u003e\u0026nbsp;\u003ca href=\"https://www.effect.website/docs/quickstart\" target=\"_blank\"\u003e\u003cimg height=\"50\" src=\"https://raw.githubusercontent.com/jpb06/jpb06/master/icons/Effect-Dark.svg\" /\u003e\u003c/a\u003e\u003c/p\u003e\n\n\u003c!-- readme-package-icons end --\u003e\n\n![example](./docs/parallel-errors-example.png)\n\n## ⚡ So how does it work?\n\nHad to re-export `runSync` and `runPromise` to apply `prettyPrint` function on the cause returned by a `catchAll`.\n\nSo using it would look like this :\n\n```typescript\nimport { runPromise } from 'effect-errors';\n\nawait runPromise(\n  Effect.gen(function* () {\n    // ...\n  }),\n);\n```\n\nYou can also directly import the `prettyPrint` function to do whatever you want with it 🤷\n\n```typescript\nimport { prettyPrint } from 'effect-errors';\n\nawait Effect.runPromise(\n  pipe(\n    Effect.gen(function* () {\n      // ...\n    }),\n    Effect.sandbox,\n    Effect.catchAll((e) =\u003e {\n      console.error(prettyPrint(e));\n\n      return Effect.fail('❌ runPromise failure');\n    }),\n  ),\n);\n```\n\nSignature is the following:\n\n```typescript\nconst prettyPrint: \u003cE\u003e(cause: Cause\u003cE\u003e, options?: PrettyPrintOptions) =\u003e string\n```\n\n`PrettyPrintOptions` allows you to tweak the following:\n\n#### `enabled` - Whether pretty printing is enabled or not\n\n\u003e default: `true`\n\n#### `stripCwd` - Whether spans and stacktrace should contain absolute or relative paths\n\n\u003e default: `false` (absolute paths)\n\n#### `hideStackTrace` - Whether node stacktrace should be displayed\n\n\u003e default: `true`\n\n## ⚡ Pretty printing from captured errors\n\nYou can also use the function `prettyPrintFromCapturedErrors` to display errors from captured errors:\n\n```ts\nimport { NodeFileSystem } from '@effect/platform-node';\nimport { Effect, pipe } from 'effect';\nimport { TaggedError } from 'effect/Data';\n\nimport { captureErrors, prettyPrintFromCapturedErrors } from 'effect-errors';\n\nexport class MyCustomError extends TaggedError('MyCustomError')\u003c{\n  cause?: unknown;\n  message?: string;\n}\u003e {}\n\nconst task = pipe(\n  Effect.fail(\n    new MyCustomError({\n      cause: 'Well this sucks',\n    }),\n  ),\n  Effect.withSpan('task', {\n    attributes: { isCool: true },\n  }),\n);\n\nconst program = pipe(\n  task,\n  Effect.sandbox,\n  Effect.catchAll((e) =\u003e\n    Effect.gen(function* () {\n      const errors = yield* captureErrors(e);\n      const message = prettyPrintFromCapturedErrors(errors, {\n        stripCwd: true,\n        hideStackTrace: true,\n      });\n\n      console.error(message);\n\n      // Do something with the captured errors ...\n    }),\n  ),\n  Effect.provide(NodeFileSystem.layer),\n  Effect.withSpan('program', { attributes: { name: 'cool' } }),\n);\n\nEffect.runPromise(program);\n```\n\nThe result would look like so:\n\n![example](./docs/pretty-print-from-captured-errors.png)\n\n## ⚡ How should I raise errors?\n\nThe best way is to use either `SchemaError` or `TaggedError`.\n\n### 🔶 `SchemaError`\n\nDeclaring the error could look like this:\n\n```typescript\nimport { Schema } from 'effect';\n\nexport class FileNotFoundError extends Schema.TaggedError\u003cSchemaError\u003e()(\n  'FileNotFound',\n  {\n    cause: Schema.Defect,\n  },\n) {}\n```\n\nYou would then raise a `FileNotFoundError` to the error channel like this:\n\n```typescript\nEffect.tryPromise({\n  try: () =\u003e ...,\n  catch: (e) =\u003e new FileNotFoundError({ cause: e }),\n});\n\n// or raising directly\nEffect.fail(new FileNotFoundError({ cause: \"Oh no!\" }));\n```\n\n### 🔶 `TaggedError`\n\n```typescript\nexport class UserNotFoundError extends TaggedError('UserNotFound')\u003c{\n  cause?: unknown;\n}\u003e {}\n```\n\nYou would then raise a `UserNotFoundError` to the error channel like this:\n\n```typescript\nEffect.tryPromise({\n  try: () =\u003e ...,\n  catch: (e) =\u003e new UserNotFoundError({ cause: e }),\n});\n\n// or raising directly\nEffect.fail(new UserNotFoundError({ cause: \"User does not exist\" }));\n```\n\n### 🔶 Plain object\n\nAlternatively, you _can_ use a plain object with a `_tag` and `message` attribute, but you won't get any stacktrace if you use this method:\n\n```typescript\nEffect.fail({ _tag: 'SucksToBeMe', message: 'Yeah...' });\n```\n\n## ⚡ Capturing errors data\n\nYou might want to apply your own logic to reported errors data; for example if you want to display errors in html. You can do so using `captureErrors`. The function has the following signature:\n\n```typescript\nexport interface ErrorSpan {\n  name: string;\n  attributes: Record\u003cstring, unknown\u003e;\n  durationInMilliseconds: number | undefined;\n  startTime: bigint;\n  endTime: bigint | undefined;\n}\n\nexport interface ErrorData {\n  errorType: unknown;\n  message: unknown;\n  stack: string[] | undefined;\n  sources: Omit\u003cErrorRelatedSources, '_tag'\u003e[] | undefined;\n  location: Omit\u003cErrorLocation, '_tag'\u003e[] | undefined;\n  spans: ErrorSpan[] | undefined;\n  isPlainString: boolean;\n}\n\nexport interface CapturedErrors {\n  interrupted: boolean;\n  errors: ErrorData[];\n}\n\nexport interface CaptureErrorsOptions {\n  stripCwd?: boolean;\n}\n\nconst captureErrors: \u003cE\u003e(\n  cause: Cause\u003cE\u003e, \n  options?: CaptureErrorsOptions\n) =\u003e Effect.Effect\u003cCapturedErrors, FsError, FileSystem\u003e\n```\n\nYou can use `captureErrors` like so:\n\n```typescript\nimport { captureErrors } from 'effect-errors';\nimport { NodeFileSystem } from '@effect/platform-node';\n\nawait Effect.runPromise(\n  pipe(\n    effect,\n    Effect.sandbox,\n    Effect.catchAll((e) =\u003e\n      Effect.gen(function* () {\n        const errors = yield* captureErrors(e);\n\n        // ...\n      }),\n    ),\n    Effect.provide(NodeFileSystem.layer),\n  ),\n);\n```\n\nCapturing errors from the [`from-promise` bundle](./src/tests/bundle/from-promise.js) would return something like this, for example:\n\n```json\n{\n  \"interrupted\": false,\n  \"errors\": [\n    {\n      \"errorType\": \"FetchError\",\n      \"message\": {\n        \"code\": \"ConnectionRefused\",\n        \"path\": \"https://yolo-bro-oh-no.org/users/123\",\n        \"errno\": 0\n      },\n      \"stack\": [\n        \"at new e (:1:28)\",\n        \"at new \u003canonymous\u003e (./src/tests/bundle/from-promise.js:31:85172)\",\n        \"at new t (:1:28)\",\n        \"at new Ga (:1:28)\",\n        \"at catch (./src/tests/bundle/from-promise.js:37:352)\",\n        \"at Sync (./src/tests/bundle/from-promise.js:31:39923)\",\n        \"at runLoop (./src/tests/bundle/from-promise.js:31:42686)\",\n        \"at evaluateEffect (./src/tests/bundle/from-promise.js:31:38196)\",\n        \"at evaluateMessageWhileSuspended (./src/tests/bundle/from-promise.js:31:37872)\",\n        \"at drainQueueOnCurrentThread (./src/tests/bundle/from-promise.js:31:35561)\",\n        \"at run (./src/tests/bundle/from-promise.js:31:43020)\",\n        \"at starveInternal (./src/tests/bundle/from-promise.js:31:6243)\",\n        \"at processTicksAndRejections (:12:39)\"\n      ],\n      \"sources\": [\n        {\n          \"name\": \"FetchError\",\n          \"runPath\": \"/Users/jpb06/repos/perso/effect-errors/src/tests/bundle/from-promise.js:37:352\",\n          \"sourcesPath\": \"/Users/jpb06/repos/perso/effect-errors/src/examples/from-promise.ts:30:13\",\n          \"source\": [\n            {\n              \"line\": 27,\n              \"code\": \"      try: async () =\u003e\"\n            },\n            {\n              \"line\": 28,\n              \"code\": \"        await fetch(`https://yolo-bro-oh-no.org/users/${userId}`),\"\n            },\n            {\n              \"line\": 29,\n              \"code\": \"      catch: (e) =\u003e\"\n            },\n            {\n              \"line\": 30,\n              \"code\": \"        new FetchError({\",\n              \"column\": 13\n            },\n            {\n              \"line\": 31,\n              \"code\": \"          cause: e,\"\n            },\n            {\n              \"line\": 32,\n              \"code\": \"        }),\"\n            },\n            {\n              \"line\": 33,\n              \"code\": \"    }),\"\n            }\n          ]\n        },\n        {\n          \"name\": \"fetchTask\",\n          \"runPath\": \"/Users/jpb06/repos/perso/effect-errors/src/tests/bundle/from-promise.js:37:213\",\n          \"sourcesPath\": \"/Users/jpb06/repos/perso/effect-errors/src/examples/from-promise.ts:25:10\",\n          \"source\": [\n            {\n              \"line\": 22,\n              \"code\": \");\"\n            },\n            {\n              \"line\": 23,\n              \"code\": \"\"\n            },\n            {\n              \"line\": 24,\n              \"code\": \"const fetchTask = (userId: string) =\u003e\"\n            },\n            {\n              \"line\": 25,\n              \"code\": \"  Effect.withSpan('fetch-user', { attributes: { userId } })(\",\n              \"column\": 10\n            },\n            {\n              \"line\": 26,\n              \"code\": \"    Effect.tryPromise({\"\n            },\n            {\n              \"line\": 27,\n              \"code\": \"      try: async () =\u003e\"\n            },\n            {\n              \"line\": 28,\n              \"code\": \"        await fetch(`https://yolo-bro-oh-no.org/users/${userId}`),\"\n            }\n          ]\n        },\n        {\n          \"name\": \"fromPromiseTask\",\n          \"runPath\": \"/Users/jpb06/repos/perso/effect-errors/src/tests/bundle/from-promise.js:37:490\",\n          \"sourcesPath\": \"/Users/jpb06/repos/perso/effect-errors/src/examples/from-promise.ts:44:39\",\n          \"source\": [\n            {\n              \"line\": 41,\n              \"code\": \"    }),\"\n            },\n            {\n              \"line\": 42,\n              \"code\": \"  );\"\n            },\n            {\n              \"line\": 43,\n              \"code\": \"\"\n            },\n            {\n              \"line\": 44,\n              \"code\": \"export const fromPromiseTask = Effect.withSpan('from-promise-task')(\",\n              \"column\": 39\n            },\n            {\n              \"line\": 45,\n              \"code\": \"  Effect.gen(function* () {\"\n            },\n            {\n              \"line\": 46,\n              \"code\": \"    yield* filename(fileName);\"\n            },\n            {\n              \"line\": 47,\n              \"code\": \"\"\n            }\n          ]\n        }\n      ],\n      \"spans\": [\n        {\n          \"name\": \"fromPromiseTask\",\n          \"attributes\": {},\n          \"durationInMilliseconds\": 246\n        },\n        {\n          \"name\": \"fetchUser\",\n          \"attributes\": {\n            \"userId\": \"123\"\n          },\n          \"durationInMilliseconds\": 239\n        }\n      ],\n      \"isPlainString\": false\n    }\n  ]\n}\n```\n\nIf no map file is found, a `location` array will be returned instead of `sources`:\n\n```json\n{\n  \"interrupted\": false,\n  \"errors\": [\n    {\n      \"errorType\": \"FetchError\",\n      \"message\": \"request to https://yolo-bro-oh-no.org/users/1 failed, reason: getaddrinfo ENOTFOUND yolo-bro-oh-no.org\",\n      \"stack\": [\n        \"at catcher (file:///Users/jpb06/repos/remix-effect-errors/build/server/nodejs-eyJydW50aW1lIjoibm9kZWpzIn0/index.js?t=1729013117205.3699:2:14550)\",\n        \"at EffectPrimitive.effect_instruction_i0 (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/internal/core-effect.ts:1694:56)\",\n        \"at body (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/internal/fiberRuntime.ts:1113:41)\",\n        \"at Object.effect_internal_function (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/Utils.ts:780:14)\",\n        \"at internalCall (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/Utils.ts:784:22)\",\n        \"at FiberRuntime.Sync (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/internal/fiberRuntime.ts:1113:19)\",\n        \"at f (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/internal/fiberRuntime.ts:1347:53)\",\n        \"at Object.context (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/internal/tracer.ts:93:19)\",\n        \"at FiberRuntime.runLoop (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/internal/fiberRuntime.ts:1337:34)\",\n        \"at FiberRuntime.evaluateEffect (file:///Users/jpb06/repos/remix-effect-errors/node_modules/effect/src/internal/fiberRuntime.ts:900:27)\"\n      ],\n      \"location\": [\n        {\n          \"filePath\": \"/build/server/nodejs-eyJydW50aW1lIjoibm9kZWpzIn0/index.js\",\n          \"line\": 2,\n          \"column\": 14414\n        },\n        {\n          \"filePath\": \"/build/server/nodejs-eyJydW50aW1lIjoibm9kZWpzIn0/index.js\",\n          \"line\": 2,\n          \"column\": 14703\n        },\n        {\n          \"filePath\": \"/build/server/nodejs-eyJydW50aW1lIjoibm9kZWpzIn0/index.js\",\n          \"line\": 2,\n          \"column\": 17636\n        }\n      ],\n      \"spans\": [\n        {\n          \"name\": \"fetch-user\",\n          \"attributes\": {\n            \"userId\": 1\n          },\n          \"durationInMilliseconds\": 52\n        },\n        {\n          \"name\": \"from-promise-task\",\n          \"attributes\": {},\n          \"durationInMilliseconds\": 54\n        },\n        {\n          \"name\": \"promise-example-loader\",\n          \"attributes\": {\n            \"url\": \"http://localhost:3000/promise\",\n            \"method\": \"GET\",\n            \"body\": null\n          },\n          \"durationInMilliseconds\": 55\n        }\n      ],\n      \"isPlainString\": false\n    }\n  ]\n}\n```\n\n## ⚡ examples\n\n### 🔶 error logging - `runPromise` / `runSync`\n\nI wrote some examples for fun and giggles. You can run them using:\n\n```bash\npnpm run-examples\n```\n\n### 🔶 Custom display for errors - `captureErrors`\n\nYou can check this [example using remix error boundaries](https://github.com/jpb06/remix-effect-errors).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpb06%2Feffect-errors","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjpb06%2Feffect-errors","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpb06%2Feffect-errors/lists"}