{"id":"916f40fb-4ccc-422e-b437-59d2e7202405","shortId":"ACKEtf","kind":"skill","title":"fp-ts-errors","tagline":"Handle errors as values using fp-ts Either and TaskEither for cleaner, more predictable TypeScript code. Use when implementing error handling patterns with fp-ts.","description":"# Practical Error Handling with fp-ts\n\nThis skill teaches you how to handle errors without try/catch spaghetti. No academic jargon - just practical patterns for real problems.\n\n## When to Use This Skill\n\n- When you want type-safe error handling in TypeScript\n- When replacing try/catch with Either and TaskEither patterns\n- When building APIs or services that need explicit error types\n- When accumulating multiple validation errors\n\nThe core idea: **Errors are just data**. Instead of throwing them into the void and hoping someone catches them, return them as values that TypeScript can track.\n\n---\n\n## 1. Stop Throwing Everywhere\n\n### The Problem with Exceptions\n\nExceptions are invisible in your types. They break the contract between functions.\n\n```typescript\n// What this function signature promises:\nfunction getUser(id: string): User\n\n// What it actually does:\nfunction getUser(id: string): User {\n  if (!id) throw new Error('ID required')\n  const user = db.find(id)\n  if (!user) throw new Error('User not found')\n  return user\n}\n\n// The caller has no idea this can fail\nconst user = getUser(id) // Might explode!\n```\n\nYou end up with code like this:\n\n```typescript\n// MESSY: try/catch everywhere\nfunction processOrder(orderId: string) {\n  let order\n  try {\n    order = getOrder(orderId)\n  } catch (e) {\n    console.error('Failed to get order')\n    return null\n  }\n\n  let user\n  try {\n    user = getUser(order.userId)\n  } catch (e) {\n    console.error('Failed to get user')\n    return null\n  }\n\n  let payment\n  try {\n    payment = chargeCard(user.cardId, order.total)\n  } catch (e) {\n    console.error('Payment failed')\n    return null\n  }\n\n  return { order, user, payment }\n}\n```\n\n### The Solution: Return Errors as Values\n\n```typescript\nimport * as E from 'fp-ts/Either'\nimport { pipe } from 'fp-ts/function'\n\n// Now TypeScript KNOWS this can fail\nfunction getUser(id: string): E.Either<string, User> {\n  if (!id) return E.left('ID required')\n  const user = db.find(id)\n  if (!user) return E.left('User not found')\n  return E.right(user)\n}\n\n// The caller is forced to handle both cases\nconst result = getUser(id)\n// result is Either<string, User> - error OR success, never both\n```\n\n---\n\n## 2. The Result Pattern (Either)\n\n`Either<E, A>` is simple: it holds either an error (`E`) or a value (`A`).\n\n- `Left` = error case\n- `Right` = success case (think \"right\" as in \"correct\")\n\n```typescript\nimport * as E from 'fp-ts/Either'\n\n// Creating values\nconst success = E.right(42)           // Right(42)\nconst failure = E.left('Oops')        // Left('Oops')\n\n// Checking what you have\nif (E.isRight(result)) {\n  console.log(result.right) // The success value\n} else {\n  console.log(result.left)  // The error\n}\n\n// Better: pattern match with fold\nconst message = pipe(\n  result,\n  E.fold(\n    (error) => `Failed: ${error}`,\n    (value) => `Got: ${value}`\n  )\n)\n```\n\n### Converting Throwing Code to Either\n\n```typescript\n// Wrap any throwing function with tryCatch\nconst parseJSON = (json: string): E.Either<Error, unknown> =>\n  E.tryCatch(\n    () => JSON.parse(json),\n    (e) => (e instanceof Error ? e : new Error(String(e)))\n  )\n\nparseJSON('{\"valid\": true}')  // Right({ valid: true })\nparseJSON('not json')          // Left(SyntaxError: ...)\n\n// For functions you'll reuse, use tryCatchK\nconst safeParseJSON = E.tryCatchK(\n  JSON.parse,\n  (e) => (e instanceof Error ? e : new Error(String(e)))\n)\n```\n\n### Common Either Operations\n\n```typescript\nimport * as E from 'fp-ts/Either'\nimport { pipe } from 'fp-ts/function'\n\n// Transform the success value\nconst doubled = pipe(\n  E.right(21),\n  E.map(n => n * 2)\n) // Right(42)\n\n// Transform the error\nconst betterError = pipe(\n  E.left('bad'),\n  E.mapLeft(e => `Error: ${e}`)\n) // Left('Error: bad')\n\n// Provide a default for errors\nconst value = pipe(\n  E.left('failed'),\n  E.getOrElse(() => 0)\n) // 0\n\n// Convert nullable to Either\nconst fromNullable = E.fromNullable('not found')\nfromNullable(user)  // Right(user) if exists, Left('not found') if null/undefined\n```\n\n---\n\n## 3. Chaining Operations That Might Fail\n\nThe real power comes from chaining. Each step can fail, but you write it as a clean pipeline.\n\n### Before: Nested Try/Catch Hell\n\n```typescript\n// MESSY: Each step can fail, nested try/catch everywhere\nfunction processUserOrder(userId: string, productId: string): Result | null {\n  let user\n  try {\n    user = getUser(userId)\n  } catch (e) {\n    logError('User fetch failed', e)\n    return null\n  }\n\n  if (!user.isActive) {\n    logError('User not active')\n    return null\n  }\n\n  let product\n  try {\n    product = getProduct(productId)\n  } catch (e) {\n    logError('Product fetch failed', e)\n    return null\n  }\n\n  if (product.stock < 1) {\n    logError('Out of stock')\n    return null\n  }\n\n  let order\n  try {\n    order = createOrder(user, product)\n  } catch (e) {\n    logError('Order creation failed', e)\n    return null\n  }\n\n  return order\n}\n```\n\n### After: Clean Chain with Either\n\n```typescript\nimport * as E from 'fp-ts/Either'\nimport { pipe } from 'fp-ts/function'\n\n// Each function returns Either<Error, T>\nconst getUser = (id: string): E.Either<string, User> => { ... }\nconst getProduct = (id: string): E.Either<string, Product> => { ... }\nconst createOrder = (user: User, product: Product): E.Either<string, Order> => { ... }\n\n// Chain them together - first error stops the chain\nconst processUserOrder = (userId: string, productId: string): E.Either<string, Order> =>\n  pipe(\n    getUser(userId),\n    E.filterOrElse(\n      user => user.isActive,\n      () => 'User not active'\n    ),\n    E.chain(user =>\n      pipe(\n        getProduct(productId),\n        E.filterOrElse(\n          product => product.stock >= 1,\n          () => 'Out of stock'\n        ),\n        E.chain(product => createOrder(user, product))\n      )\n    )\n  )\n\n// Or use Do notation for cleaner access to intermediate values\nconst processUserOrder = (userId: string, productId: string): E.Either<string, Order> =>\n  pipe(\n    E.Do,\n    E.bind('user', () => getUser(userId)),\n    E.filterOrElse(\n      ({ user }) => user.isActive,\n      () => 'User not active'\n    ),\n    E.bind('product', () => getProduct(productId)),\n    E.filterOrElse(\n      ({ product }) => product.stock >= 1,\n      () => 'Out of stock'\n    ),\n    E.chain(({ user, product }) => createOrder(user, product))\n  )\n```\n\n### Different Error Types? Use chainW\n\n```typescript\ntype ValidationError = { type: 'validation'; message: string }\ntype DbError = { type: 'db'; message: string }\n\nconst validateInput = (id: string): E.Either<ValidationError, string> => { ... }\nconst fetchFromDb = (id: string): E.Either<DbError, User> => { ... }\n\n// chainW (W = \"wider\") automatically unions the error types\nconst process = (id: string): E.Either<ValidationError | DbError, User> =>\n  pipe(\n    validateInput(id),\n    E.chainW(validId => fetchFromDb(validId))\n  )\n```\n\n---\n\n## 4. Collecting Multiple Errors\n\nSometimes you want ALL errors, not just the first one. Form validation is the classic example.\n\n### Before: Collecting Errors Manually\n\n```typescript\n// MESSY: Manual error accumulation\nfunction validateForm(form: FormData): { valid: boolean; errors: string[] } {\n  const errors: string[] = []\n\n  if (!form.email) {\n    errors.push('Email required')\n  } else if (!form.email.includes('@')) {\n    errors.push('Invalid email')\n  }\n\n  if (!form.password) {\n    errors.push('Password required')\n  } else if (form.password.length < 8) {\n    errors.push('Password too short')\n  }\n\n  if (!form.age) {\n    errors.push('Age required')\n  } else if (form.age < 18) {\n    errors.push('Must be 18+')\n  }\n\n  return { valid: errors.length === 0, errors }\n}\n```\n\n### After: Validation with Error Accumulation\n\n```typescript\nimport * as E from 'fp-ts/Either'\nimport * as NEA from 'fp-ts/NonEmptyArray'\nimport { sequenceS } from 'fp-ts/Apply'\nimport { pipe } from 'fp-ts/function'\n\n// Errors as a NonEmptyArray (always at least one)\ntype Errors = NEA.NonEmptyArray<string>\n\n// Create the applicative that accumulates errors\nconst validation = E.getApplicativeValidation(NEA.getSemigroup<string>())\n\n// Validators that return Either<Errors, T>\nconst validateEmail = (email: string): E.Either<Errors, string> =>\n  !email ? E.left(NEA.of('Email required'))\n  : !email.includes('@') ? E.left(NEA.of('Invalid email'))\n  : E.right(email)\n\nconst validatePassword = (password: string): E.Either<Errors, string> =>\n  !password ? E.left(NEA.of('Password required'))\n  : password.length < 8 ? E.left(NEA.of('Password too short'))\n  : E.right(password)\n\nconst validateAge = (age: number | undefined): E.Either<Errors, number> =>\n  age === undefined ? E.left(NEA.of('Age required'))\n  : age < 18 ? E.left(NEA.of('Must be 18+'))\n  : E.right(age)\n\n// Combine all validations - collects ALL errors\nconst validateForm = (form: FormData) =>\n  sequenceS(validation)({\n    email: validateEmail(form.email),\n    password: validatePassword(form.password),\n    age: validateAge(form.age)\n  })\n\n// Usage\nvalidateForm({ email: '', password: '123', age: 15 })\n// Left(['Email required', 'Password too short', 'Must be 18+'])\n\nvalidateForm({ email: 'a@b.com', password: 'longpassword', age: 25 })\n// Right({ email: 'a@b.com', password: 'longpassword', age: 25 })\n```\n\n### Field-Level Errors for Forms\n\n```typescript\ninterface FieldError {\n  field: string\n  message: string\n}\n\ntype FormErrors = NEA.NonEmptyArray<FieldError>\n\nconst fieldError = (field: string, message: string): FormErrors =>\n  NEA.of({ field, message })\n\nconst formValidation = E.getApplicativeValidation(NEA.getSemigroup<FieldError>())\n\n// Now errors know which field they belong to\nconst validateEmail = (email: string): E.Either<FormErrors, string> =>\n  !email ? E.left(fieldError('email', 'Required'))\n  : !email.includes('@') ? E.left(fieldError('email', 'Invalid format'))\n  : E.right(email)\n\n// Easy to display in UI\nconst getFieldError = (errors: FormErrors, field: string): string | undefined =>\n  errors.find(e => e.field === field)?.message\n```\n\n---\n\n## 5. Async Operations (TaskEither)\n\nFor async operations that can fail, use `TaskEither`. It's like `Either` but for promises.\n\n- `TaskEither<E, A>` = a function that returns `Promise<Either<E, A>>`\n- Lazy: nothing runs until you execute it\n\n```typescript\nimport * as TE from 'fp-ts/TaskEither'\nimport { pipe } from 'fp-ts/function'\n\n// Wrap any async operation\nconst fetchUser = (id: string): TE.TaskEither<Error, User> =>\n  TE.tryCatch(\n    () => fetch(`/api/users/${id}`).then(r => r.json()),\n    (e) => (e instanceof Error ? e : new Error(String(e)))\n  )\n\n// Chain async operations - just like Either\nconst getUserPosts = (userId: string): TE.TaskEither<Error, Post[]> =>\n  pipe(\n    fetchUser(userId),\n    TE.chain(user => fetchPosts(user.id))\n  )\n\n// Execute when ready\nconst result = await getUserPosts('123')() // Returns Either<Error, Post[]>\n```\n\n### Before: Promise Chain with Error Handling\n\n```typescript\n// MESSY: try/catch mixed with promise chains\nasync function loadDashboard(userId: string) {\n  try {\n    const user = await fetchUser(userId)\n    if (!user) throw new Error('User not found')\n\n    let posts, notifications, settings\n    try {\n      [posts, notifications, settings] = await Promise.all([\n        fetchPosts(user.id),\n        fetchNotifications(user.id),\n        fetchSettings(user.id)\n      ])\n    } catch (e) {\n      // Which one failed? Who knows!\n      console.error('Failed to load data', e)\n      return null\n    }\n\n    return { user, posts, notifications, settings }\n  } catch (e) {\n    console.error('Failed to load user', e)\n    return null\n  }\n}\n```\n\n### After: Clean TaskEither Pipeline\n\n```typescript\nimport * as TE from 'fp-ts/TaskEither'\nimport { sequenceS } from 'fp-ts/Apply'\nimport { pipe } from 'fp-ts/function'\n\nconst loadDashboard = (userId: string) =>\n  pipe(\n    fetchUser(userId),\n    TE.chain(user =>\n      pipe(\n        // Parallel fetch with sequenceS\n        sequenceS(TE.ApplyPar)({\n          posts: fetchPosts(user.id),\n          notifications: fetchNotifications(user.id),\n          settings: fetchSettings(user.id)\n        }),\n        TE.map(data => ({ user, ...data }))\n      )\n    )\n  )\n\n// Execute and handle both cases\npipe(\n  loadDashboard('123'),\n  TE.fold(\n    (error) => T.of(renderError(error)),\n    (data) => T.of(renderDashboard(data))\n  )\n)()\n```\n\n### Retry Failed Operations\n\n```typescript\nimport * as T from 'fp-ts/Task'\nimport * as TE from 'fp-ts/TaskEither'\nimport { pipe } from 'fp-ts/function'\n\nconst retry = <E, A>(\n  task: TE.TaskEither<E, A>,\n  attempts: number,\n  delayMs: number\n): TE.TaskEither<E, A> =>\n  pipe(\n    task,\n    TE.orElse((error) =>\n      attempts > 1\n        ? pipe(\n            T.delay(delayMs)(T.of(undefined)),\n            T.chain(() => retry(task, attempts - 1, delayMs * 2))\n          )\n        : TE.left(error)\n    )\n  )\n\n// Retry up to 3 times with exponential backoff\nconst fetchWithRetry = retry(fetchUser('123'), 3, 1000)\n```\n\n### Fallback to Alternative\n\n```typescript\n// Try cache first, fall back to API\nconst getUserData = (id: string) =>\n  pipe(\n    fetchFromCache(id),\n    TE.orElse(() => fetchFromApi(id)),\n    TE.orElse(() => TE.right(defaultUser)) // Last resort default\n  )\n```\n\n---\n\n## 6. Converting Between Patterns\n\nReal codebases have throwing functions, nullable values, and promises. Here's how to work with them.\n\n### From Nullable to Either\n\n```typescript\nimport * as E from 'fp-ts/Either'\nimport * as O from 'fp-ts/Option'\n\n// Direct conversion\nconst user = users.find(u => u.id === id) // User | undefined\nconst result = E.fromNullable('User not found')(user)\n\n// From Option\nconst maybeUser: O.Option<User> = O.fromNullable(user)\nconst eitherUser = pipe(\n  maybeUser,\n  E.fromOption(() => 'User not found')\n)\n```\n\n### From Throwing Function to Either\n\n```typescript\n// Wrap at the boundary\nconst safeParse = <T>(schema: ZodSchema<T>) => (data: unknown): E.Either<ZodError, T> =>\n  E.tryCatch(\n    () => schema.parse(data),\n    (e) => e as ZodError\n  )\n\n// Use throughout your code\nconst parseUser = safeParse(UserSchema)\nconst result = parseUser(rawData) // Either<ZodError, User>\n```\n\n### From Promise to TaskEither\n\n```typescript\nimport * as TE from 'fp-ts/TaskEither'\n\n// Wrap external async functions\nconst fetchJson = <T>(url: string): TE.TaskEither<Error, T> =>\n  TE.tryCatch(\n    () => fetch(url).then(r => r.json()),\n    (e) => new Error(`Fetch failed: ${e}`)\n  )\n\n// Wrap axios, prisma, any async library\nconst getUserFromDb = (id: string): TE.TaskEither<DbError, User> =>\n  TE.tryCatch(\n    () => prisma.user.findUniqueOrThrow({ where: { id } }),\n    (e) => ({ code: 'DB_ERROR', cause: e })\n  )\n```\n\n### Back to Promise (Escape Hatch)\n\nSometimes you need a plain Promise for external APIs.\n\n```typescript\nimport * as TE from 'fp-ts/TaskEither'\nimport * as E from 'fp-ts/Either'\n\nconst myTaskEither: TE.TaskEither<Error, User> = fetchUser('123')\n\n// Option 1: Get the Either (preserves both cases)\nconst either: E.Either<Error, User> = await myTaskEither()\n\n// Option 2: Throw on error (for legacy code)\nconst toThrowingPromise = <E, A>(te: TE.TaskEither<E, A>): Promise<A> =>\n  te().then(E.fold(\n    (error) => Promise.reject(error),\n    (value) => Promise.resolve(value)\n  ))\n\nconst user = await toThrowingPromise(fetchUser('123')) // Throws if Left\n\n// Option 3: Default on error\nconst user = await pipe(\n  fetchUser('123'),\n  TE.getOrElse(() => T.of(defaultUser))\n)()\n```\n\n---\n\n## Real Scenarios\n\n### Parse User Input Safely\n\n```typescript\ninterface ParsedInput {\n  id: number\n  name: string\n  tags: string[]\n}\n\nconst parseInput = (raw: unknown): E.Either<string, ParsedInput> =>\n  pipe(\n    E.Do,\n    E.bind('obj', () =>\n      typeof raw === 'object' && raw !== null\n        ? E.right(raw as Record<string, unknown>)\n        : E.left('Input must be an object')\n    ),\n    E.bind('id', ({ obj }) =>\n      typeof obj.id === 'number'\n        ? E.right(obj.id)\n        : E.left('id must be a number')\n    ),\n    E.bind('name', ({ obj }) =>\n      typeof obj.name === 'string' && obj.name.length > 0\n        ? E.right(obj.name)\n        : E.left('name must be a non-empty string')\n    ),\n    E.bind('tags', ({ obj }) =>\n      Array.isArray(obj.tags) && obj.tags.every(t => typeof t === 'string')\n        ? E.right(obj.tags as string[])\n        : E.left('tags must be an array of strings')\n    ),\n    E.map(({ id, name, tags }) => ({ id, name, tags }))\n  )\n\n// Usage\nparseInput({ id: 1, name: 'test', tags: ['a', 'b'] })\n// Right({ id: 1, name: 'test', tags: ['a', 'b'] })\n\nparseInput({ id: 'wrong', name: '', tags: null })\n// Left('id must be a number')\n```\n\n### API Call with Full Error Handling\n\n```typescript\ninterface ApiError {\n  code: string\n  message: string\n  status?: number\n}\n\nconst createApiError = (message: string, code = 'UNKNOWN', status?: number): ApiError =>\n  ({ code, message, status })\n\nconst fetchWithErrorHandling = <T>(url: string): TE.TaskEither<ApiError, T> =>\n  pipe(\n    TE.tryCatch(\n      () => fetch(url),\n      () => createApiError('Network error', 'NETWORK')\n    ),\n    TE.chain(response =>\n      response.ok\n        ? TE.tryCatch(\n            () => response.json() as Promise<T>,\n            () => createApiError('Invalid JSON', 'PARSE')\n          )\n        : TE.left(createApiError(\n            `HTTP ${response.status}`,\n            response.status === 404 ? 'NOT_FOUND' : 'HTTP_ERROR',\n            response.status\n          ))\n    )\n  )\n\n// Usage with pattern matching on error codes\nconst handleUserFetch = (userId: string) =>\n  pipe(\n    fetchWithErrorHandling<User>(`/api/users/${userId}`),\n    TE.fold(\n      (error) => {\n        switch (error.code) {\n          case 'NOT_FOUND': return T.of(showNotFoundPage())\n          case 'NETWORK': return T.of(showOfflineMessage())\n          default: return T.of(showGenericError(error.message))\n        }\n      },\n      (user) => T.of(showUserProfile(user))\n    )\n  )\n```\n\n### Process List Where Some Items Might Fail\n\n```typescript\nimport * as A from 'fp-ts/Array'\nimport * as E from 'fp-ts/Either'\nimport { pipe } from 'fp-ts/function'\n\ninterface ProcessResult<T> {\n  successes: T[]\n  failures: Array<{ item: unknown; error: string }>\n}\n\n// Process all, collect successes and failures separately\nconst processAllCollectErrors = <T, R>(\n  items: T[],\n  process: (item: T) => E.Either<string, R>\n): ProcessResult<R> => {\n  const results = items.map((item, index) =>\n    pipe(\n      process(item),\n      E.mapLeft(error => ({ item, error, index }))\n    )\n  )\n\n  return {\n    successes: pipe(results, A.filterMap(E.toOption)),\n    failures: pipe(\n      results,\n      A.filterMap(r => E.isLeft(r) ? O.some(r.left) : O.none)\n    )\n  }\n}\n\n// Usage\nconst parseNumbers = (inputs: string[]) =>\n  processAllCollectErrors(inputs, input => {\n    const n = parseInt(input, 10)\n    return isNaN(n) ? E.left(`Invalid number: ${input}`) : E.right(n)\n  })\n\nparseNumbers(['1', 'abc', '3', 'def'])\n// {\n//   successes: [1, 3],\n//   failures: [\n//     { item: 'abc', error: 'Invalid number: abc', index: 1 },\n//     { item: 'def', error: 'Invalid number: def', index: 3 }\n//   ]\n// }\n```\n\n### Bulk Operations with Partial Success\n\n```typescript\nimport * as TE from 'fp-ts/TaskEither'\nimport * as T from 'fp-ts/Task'\nimport { pipe } from 'fp-ts/function'\n\ninterface BulkResult<T> {\n  succeeded: T[]\n  failed: Array<{ id: string; error: string }>\n}\n\nconst bulkProcess = <T>(\n  ids: string[],\n  process: (id: string) => TE.TaskEither<string, T>\n): T.Task<BulkResult<T>> =>\n  pipe(\n    ids,\n    A.map(id =>\n      pipe(\n        process(id),\n        TE.fold(\n          (error) => T.of({ type: 'failed' as const, id, error }),\n          (result) => T.of({ type: 'succeeded' as const, result })\n        )\n      )\n    ),\n    T.sequenceArray,\n    T.map(results => ({\n      succeeded: results\n        .filter((r): r is { type: 'succeeded'; result: T } => r.type === 'succeeded')\n        .map(r => r.result),\n      failed: results\n        .filter((r): r is { type: 'failed'; id: string; error: string } => r.type === 'failed')\n        .map(({ id, error }) => ({ id, error }))\n    }))\n  )\n\n// Usage\nconst deleteUsers = (userIds: string[]) =>\n  bulkProcess(userIds, id =>\n    pipe(\n      deleteUser(id),\n      TE.mapLeft(e => e.message)\n    )\n  )\n\n// All operations run, you get a report of what worked and what didn't\n```\n\n---\n\n## Quick Reference\n\n| Pattern | Use When | Example |\n|---------|----------|---------|\n| `E.right(value)` | Creating a success | `E.right(42)` |\n| `E.left(error)` | Creating a failure | `E.left('not found')` |\n| `E.tryCatch(fn, onError)` | Wrapping throwing code | `E.tryCatch(() => JSON.parse(s), toError)` |\n| `E.fromNullable(error)` | Converting nullable | `E.fromNullable('missing')(maybeValue)` |\n| `E.map(fn)` | Transform success | `pipe(result, E.map(x => x * 2))` |\n| `E.mapLeft(fn)` | Transform error | `pipe(result, E.mapLeft(addContext))` |\n| `E.chain(fn)` | Chain operations | `pipe(getA(), E.chain(a => getB(a.id)))` |\n| `E.chainW(fn)` | Chain with different error type | `pipe(validate(), E.chainW(save))` |\n| `E.fold(onError, onSuccess)` | Handle both cases | `E.fold(showError, showData)` |\n| `E.getOrElse(onError)` | Extract with default | `E.getOrElse(() => 0)` |\n| `E.filterOrElse(pred, onFalse)` | Validate with error | `E.filterOrElse(x => x > 0, () => 'must be positive')` |\n| `sequenceS(validation)({...})` | Collect all errors | Form validation |\n\n### TaskEither Equivalents\n\nAll Either operations have TaskEither equivalents:\n- `TE.right`, `TE.left`, `TE.tryCatch`\n- `TE.map`, `TE.mapLeft`, `TE.chain`, `TE.chainW`\n- `TE.fold`, `TE.getOrElse`, `TE.filterOrElse`\n- `TE.orElse` for fallbacks\n\n---\n\n## Summary\n\n1. **Return errors as values** - Use Either/TaskEither instead of throwing\n2. **Chain with confidence** - `chain` stops at first error automatically\n3. **Collect all errors when needed** - Use validation applicative for forms\n4. **Wrap at boundaries** - Convert throwing/Promise code at the edges\n5. **Match at the end** - Use `fold` to handle both cases when you're ready to act\n\nThe payoff: TypeScript tracks your errors, no more forgotten try/catch, clear control flow, and composable error handling.\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","tags":["errors","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity-skills"],"capabilities":["skill","source-sickn33","skill-fp-ts-errors","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/fp-ts-errors","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34793 github stars · SKILL.md body (22,463 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-24T00:50:58.651Z","embedding":null,"createdAt":"2026-04-18T21:37:31.289Z","updatedAt":"2026-04-24T00:50:58.651Z","lastSeenAt":"2026-04-24T00:50:58.651Z","tsv":"'/api/users':1277,2064 '/apply':986,1420 '/array':2105 '/either':276,378,499,693,971,1610,1781,2113 '/function':283,506,700,993,1263,1427,1500,2120,2255 '/nonemptyarray':979 '/option':1618 '/task':1485,2248 '/taskeither':1256,1413,1493,1704,1773,2240 '0':548,549,956,1917,2458,2468 '1':124,655,764,811,1521,1531,1790,1961,1969,2203,2208,2218,2501 '10':2192 '1000':1550 '123':1109,1318,1464,1548,1788,1835,1849 '15':1111 '18':948,952,1076,1081,1120 '2':339,519,1533,1805,2413,2511 '21':515 '25':1127,1134 '3':570,1539,1549,1840,2205,2209,2226,2521 '4':876,2532 '404':2045 '42':384,386,521,2378 '5':1211,2542 '6':1578 '8':935,1053 'a.filtermap':2168,2173 'a.id':2431 'a.map':2280 'a@b.com':1123,1130 'abc':2204,2212,2216 'academ':51 'access':779 'accumul':93,904,962,1009 'act':2558 'activ':635,755,803 'actual':157 'addcontext':2421 'age':943,1063,1069,1073,1075,1083,1102,1110,1126,1133 'altern':1553 'alway':998 'api':84,1561,1764,1987 'apierror':1995,2010,2019 'applic':1007,2529 'array':1948,2126,2261 'array.isarray':1932 'ask':2609 'async':1212,1216,1266,1292,1336,1707,1732 'attempt':1509,1520,1530 'automat':856,2520 'await':1316,1344,1363,1802,1832,1846 'axio':1729 'b':1966,1974 'back':1559,1751 'backoff':1543 'bad':529,536 'belong':1171 'better':410 'bettererror':526 'boolean':910 'boundari':1660,2535,2617 'break':139 'build':83 'bulk':2227 'bulkprocess':2267,2343 'bulkresult':2257,2277 'cach':1556 'call':1988 'caller':186,318 'case':324,361,364,1461,1796,2070,2076,2448,2552 'catch':114,220,235,251,621,644,669,1371,1391 'caus':1749 'chain':571,581,682,730,737,1291,1325,1335,2424,2434,2512,2515 'chainw':825,853 'chargecard':248 'check':393 'clarif':2611 'classic':894 'clean':592,681,1402 'cleaner':17,778 'clear':2569,2584 'code':21,203,428,1680,1746,1811,1996,2006,2011,2057,2392,2538 'codebas':1583 'collect':877,897,1087,2133,2474,2522 'combin':1084 'come':579 'common':488 'compos':2573 'confid':2514 'console.error':222,237,253,1378,1393 'console.log':400,406 'const':171,193,303,325,381,387,415,438,475,511,525,542,554,707,714,721,738,783,839,846,861,913,1011,1021,1040,1061,1090,1151,1161,1173,1198,1268,1297,1314,1342,1428,1501,1544,1562,1621,1629,1638,1643,1661,1681,1685,1709,1734,1782,1797,1812,1830,1844,1868,2002,2014,2058,2138,2151,2181,2188,2266,2291,2299,2339 'contract':141 'control':2570 'convers':1620 'convert':426,550,1579,2399,2536 'core':98 'correct':369 'creat':379,1005,2374,2381 'createapierror':2003,2025,2036,2041 'createord':666,722,770,818 'creation':673 'criteria':2620 'data':103,1382,1454,1456,1470,1473,1665,1672 'db':836,1747 'db.find':173,305 'dberror':834,851,867,1739 'def':2206,2220,2224 'default':539,1577,1841,2081,2456 'defaultus':1574,1852 'delaym':1511,1524,1532 'deleteus':2340,2347 'describ':2588 'didn':2364 'differ':821,2436 'direct':1619 'display':1195 'doubl':512 'e':221,236,252,271,345,354,373,448,449,452,456,479,480,483,487,494,531,533,622,627,645,650,670,675,688,966,1207,1231,1239,1282,1283,1286,1290,1372,1383,1392,1398,1503,1507,1514,1605,1673,1674,1722,1727,1745,1750,1776,1814,1818,2108,2350 'e.bind':794,804,1877,1896,1910,1929 'e.chain':756,768,815,2422,2428 'e.chainw':872,2432,2441 'e.do':793,1876 'e.either':294,442,711,718,727,744,789,843,850,865,1025,1044,1066,1177,1667,1799,1872,2147 'e.field':1208 'e.filterorelse':750,761,798,808,2459,2465 'e.fold':419,1823,2443,2449 'e.fromnullable':556,1631,2397,2401 'e.fromoption':1647 'e.getapplicativevalidation':1013,1163 'e.getorelse':547,2452,2457 'e.isleft':2175 'e.isright':398 'e.left':300,310,389,528,545,1029,1034,1048,1054,1071,1077,1181,1186,1890,1904,1920,1943,2196,2379,2384 'e.map':516,1951,2404,2410 'e.mapleft':530,2159,2414,2420 'e.message':2351 'e.right':315,383,514,1038,1059,1082,1191,1884,1902,1918,1939,2200,2372,2377 'e.tooption':2169 'e.trycatch':445,1670,2387,2393 'e.trycatchk':477 'easi':1193 'edg':2541 'either':13,78,331,343,344,351,430,489,553,684,704,1018,1226,1238,1296,1320,1601,1655,1689,1793,1798,2482 'either/taskeither':2507 'eitherus':1644 'els':405,921,932,945 'email':919,926,1023,1028,1031,1037,1039,1096,1107,1113,1122,1129,1175,1180,1183,1188,1192 'email.includes':1033,1185 'empti':1927 'end':200,2546 'environ':2600 'environment-specif':2599 'equival':2480,2486 'error':4,6,25,33,46,70,90,96,100,168,179,265,334,353,360,409,420,422,443,451,454,482,485,524,532,535,541,705,734,822,859,879,884,898,903,911,914,957,961,994,1003,1010,1019,1026,1045,1067,1089,1138,1166,1200,1273,1285,1288,1302,1321,1327,1351,1466,1469,1519,1535,1714,1724,1748,1785,1800,1808,1824,1826,1843,1991,2027,2049,2056,2067,2129,2160,2162,2213,2221,2264,2286,2293,2329,2335,2337,2380,2398,2417,2437,2464,2476,2503,2519,2524,2564,2574 'error.code':2069 'error.message':2085 'errors.find':1206 'errors.length':955 'errors.push':918,924,929,936,942,949 'escap':1754 'everywher':127,209,606 'exampl':895,2371 'except':131,132 'execut':1246,1311,1457 'exist':564 'expert':2605 'explicit':89 'explod':198 'exponenti':1542 'extern':1706,1763 'extract':2454 'fail':192,223,238,255,289,421,546,575,585,603,626,649,674,1220,1375,1379,1394,1475,1726,2096,2260,2289,2319,2326,2332 'failur':388,2125,2136,2170,2210,2383 'fall':1558 'fallback':1551,2499 'fetch':625,648,1276,1439,1717,1725,2023 'fetchfromapi':1570 'fetchfromcach':1567 'fetchfromdb':847,874 'fetchjson':1710 'fetchnotif':1367,1448 'fetchpost':1309,1365,1445 'fetchset':1369,1451 'fetchus':1269,1305,1345,1433,1547,1787,1834,1848 'fetchwitherrorhandl':2015,2063 'fetchwithretri':1545 'field':1136,1144,1153,1159,1169,1202,1209 'field-level':1135 'fielderror':1143,1152,1182,1187 'filter':2306,2321 'first':733,888,1557,2518 'flow':2571 'fn':2388,2405,2415,2423,2433 'fold':414,2548 'forc':320 'forgotten':2567 'form':890,907,1092,1140,2477,2531 'form.age':941,947,1104 'form.email':917,1098 'form.email.includes':923 'form.password':928,1101 'form.password.length':934 'format':1190 'formdata':908,1093 'formerror':1149,1157,1178,1201 'formvalid':1162 'found':182,313,558,567,1354,1634,1650,2047,2072,2386 'fp':2,11,30,37,274,281,376,497,504,691,698,969,977,984,991,1254,1261,1411,1418,1425,1483,1491,1498,1608,1616,1702,1771,1779,2103,2111,2118,2238,2246,2253 'fp-ts':10,29,36,273,280,375,496,503,690,697,968,976,983,990,1253,1260,1410,1417,1424,1482,1490,1497,1607,1615,1701,1770,1778,2102,2110,2117,2237,2245,2252 'fp-ts-error':1 'fromnul':555,559 'full':1990 'function':143,147,150,159,210,290,435,469,607,702,905,1234,1337,1586,1653,1708 'get':225,240,1791,2356 'geta':2427 'getb':2430 'getfielderror':1199 'getord':218 'getproduct':642,715,759,806 'getus':151,160,195,233,291,327,619,708,748,796 'getuserdata':1563 'getuserfromdb':1735 'getuserpost':1298,1317 'got':424 'handl':5,26,34,45,71,322,1328,1459,1992,2446,2550,2575 'handleuserfetch':2059 'hatch':1755 'hell':597 'hold':350 'hope':112 'http':2042,2048 'id':152,161,165,169,174,196,292,298,301,306,328,709,716,841,848,863,871,1270,1278,1564,1568,1571,1626,1736,1744,1862,1897,1905,1952,1955,1960,1968,1976,1982,2262,2268,2271,2279,2281,2284,2292,2327,2334,2336,2345,2348 'idea':99,189 'implement':24 'import':269,277,371,492,500,686,694,964,972,980,987,1249,1257,1406,1414,1421,1478,1486,1494,1603,1611,1697,1766,1774,2098,2106,2114,2233,2241,2249 'index':2155,2163,2217,2225 'input':1857,1891,2183,2186,2187,2191,2199,2614 'instanceof':450,481,1284 'instead':104,2508 'interfac':1142,1860,1994,2121,2256 'intermedi':781 'invalid':925,1036,1189,2037,2197,2214,2222 'invis':134 'isnan':2194 'item':2094,2127,2142,2145,2154,2158,2161,2211,2219 'items.map':2153 'jargon':52 'json':440,447,465,2038 'json.parse':446,478,2394 'know':286,1167,1377 'last':1575 'lazi':1241 'least':1000 'left':359,391,466,534,565,1112,1838,1981 'legaci':1810 'let':214,229,244,615,638,662,1355 'level':1137 'librari':1733 'like':204,1225,1295 'limit':2576 'list':2091 'll':471 'load':1381,1396 'loaddashboard':1338,1429,1463 'logerror':623,632,646,656,671 'longpassword':1125,1132 'manual':899,902 'map':2316,2333 'match':412,2054,2543,2585 'maybeus':1639,1646 'maybevalu':2403 'messag':416,831,837,1146,1155,1160,1210,1998,2004,2012 'messi':207,599,901,1330 'might':197,574,2095 'miss':2402,2622 'mix':1332 'multipl':94,878 'must':950,1079,1118,1892,1906,1922,1945,1983,2469 'mytaskeith':1783,1803 'n':517,518,2189,2195,2201 'name':1864,1911,1921,1953,1956,1962,1970,1978 'nea':974 'nea.getsemigroup':1014,1164 'nea.nonemptyarray':1004,1150 'nea.of':1030,1035,1049,1055,1072,1078,1158 'need':88,1758,2526 'nest':595,604 'network':2026,2028,2077 'never':337 'new':167,178,453,484,1287,1350,1723 'non':1926 'non-empti':1925 'nonemptyarray':997 'notat':776 'noth':1242 'notif':1357,1361,1389,1447 'null':228,243,257,614,629,637,652,661,677,1385,1400,1883,1980 'null/undefined':569 'nullabl':551,1587,1599,2400 'number':1064,1068,1510,1512,1863,1901,1909,1986,2001,2009,2198,2215,2223 'o':1613 'o.fromnullable':1641 'o.none':2179 'o.option':1640 'o.some':2177 'obj':1878,1898,1912,1931 'obj.id':1900,1903 'obj.name':1914,1919 'obj.name.length':1916 'obj.tags':1933,1940 'obj.tags.every':1934 'object':1881,1895 'one':889,1001,1374 'onerror':2389,2444,2453 'onfals':2461 'onsuccess':2445 'oop':390,392 'oper':490,572,1213,1217,1267,1293,1476,2228,2353,2425,2483 'option':1637,1789,1804,1839 'order':215,217,226,259,663,665,672,679,729,746,791 'order.total':250 'order.userid':234 'orderid':212,219 'output':2594 'parallel':1438 'pars':1855,2039 'parsedinput':1861,1874 'parseinput':1869,1959,1975 'parseint':2190 'parsejson':439,457,463 'parsenumb':2182,2202 'parseus':1682,1687 'partial':2230 'password':930,937,1042,1047,1050,1056,1060,1099,1108,1115,1124,1131 'password.length':1052 'pattern':27,55,81,342,411,1581,2053,2368 'payment':245,247,254,261 'payoff':2560 'permiss':2615 'pipe':278,417,501,513,527,544,695,747,758,792,869,988,1258,1304,1422,1432,1437,1462,1495,1516,1522,1566,1645,1847,1875,2021,2062,2115,2156,2166,2171,2250,2278,2282,2346,2408,2418,2426,2439 'pipelin':593,1404 'plain':1760 'posit':2471 'post':1303,1322,1356,1360,1388,1444 'power':578 'practic':32,54 'pred':2460 'predict':19 'preserv':1794 'prisma':1730 'prisma.user.finduniqueorthrow':1742 'problem':58,129 'process':862,2090,2131,2144,2157,2270,2283 'processallcollecterror':2139,2185 'processord':211 'processresult':2122,2150 'processuserord':608,739,784 'product':639,641,647,668,720,725,726,762,769,772,805,809,817,820 'product.stock':654,763,810 'productid':611,643,742,760,787,807 'promis':149,1229,1237,1324,1334,1590,1693,1753,1761,1820,2035 'promise.all':1364 'promise.reject':1825 'promise.resolve':1828 'provid':537 'quick':2366 'r':1280,1720,2141,2149,2174,2176,2307,2308,2317,2322,2323 'r.json':1281,1721 'r.left':2178 'r.result':2318 'r.type':2314,2331 'raw':1870,1880,1882,1885 'rawdata':1688 're':2555 'readi':1313,2556 'real':57,577,1582,1853 'record':1887 'refer':2367 'renderdashboard':1472 'rendererror':1468 'replac':75 'report':2358 'requir':170,302,920,931,944,1032,1051,1074,1114,1184,2613 'resort':1576 'respons':2030 'response.json':2033 'response.ok':2031 'response.status':2043,2044,2050 'result':326,329,341,399,418,613,1315,1630,1686,2152,2167,2172,2294,2300,2303,2305,2312,2320,2409,2419 'result.left':407 'result.right':401 'retri':1474,1502,1528,1536,1546 'return':116,183,227,242,256,258,264,299,309,314,628,636,651,660,676,678,703,953,1017,1236,1319,1384,1386,1399,2073,2078,2082,2164,2193,2502 'reus':472 'review':2606 'right':362,366,385,460,520,561,1128,1967 'run':1243,2354 'safe':69,1858 'safepars':1662,1683 'safeparsejson':476 'safeti':2616 'save':2442 'scenario':1854 'schema':1663 'schema.parse':1671 'scope':2587 'separ':2137 'sequenc':981,1094,1415,1441,1442,2472 'servic':86 'set':1358,1362,1390,1450 'short':939,1058,1117 'showdata':2451 'showerror':2450 'showgenericerror':2084 'shownotfoundpag':2075 'showofflinemessag':2080 'showuserprofil':2088 'signatur':148 'simpl':348 'skill':40,63,2579 'skill-fp-ts-errors' 'solut':263 'someon':113 'sometim':880,1756 'source-sickn33' 'spaghetti':49 'specif':2601 'status':2000,2008,2013 'step':583,601 'stock':659,767,814 'stop':125,735,2516,2607 'string':153,162,213,293,295,332,441,455,486,610,612,710,712,717,719,728,741,743,745,786,788,790,832,838,842,845,849,864,912,915,1024,1027,1043,1046,1145,1147,1154,1156,1176,1179,1203,1204,1271,1289,1300,1340,1431,1565,1712,1737,1865,1867,1873,1888,1915,1928,1938,1942,1950,1997,1999,2005,2017,2061,2130,2148,2184,2263,2265,2269,2272,2274,2328,2330,2342 'substitut':2597 'succeed':2258,2297,2304,2311,2315 'success':336,363,382,403,509,2123,2134,2165,2207,2231,2376,2407,2619 'summari':2500 'switch':2068 'syntaxerror':467 't.chain':1527 't.delay':1523 't.map':2302 't.of':1467,1471,1525,1851,2074,2079,2083,2087,2287,2295 't.sequencearray':2301 't.task':2276 'tag':1866,1930,1944,1954,1957,1964,1972,1979 'task':1505,1517,1529,2583 'taskeith':15,80,1214,1222,1230,1403,1695,2479,2485 'te':1251,1408,1488,1699,1768,1816,1821,2235 'te.applypar':1443 'te.chain':1307,1435,2029,2492 'te.chainw':2493 'te.filterorelse':2496 'te.fold':1465,2066,2285,2494 'te.getorelse':1850,2495 'te.left':1534,2040,2488 'te.map':1453,2490 'te.mapleft':2349,2491 'te.orelse':1518,1569,1572,2497 'te.right':1573,2487 'te.taskeither':1272,1301,1506,1513,1713,1738,1784,1817,2018,2273 'te.trycatch':1275,1716,1741,2022,2032,2489 'teach':41 'test':1963,1971,2603 'think':365 'throughout':1678 'throw':106,126,166,177,427,434,1349,1585,1652,1806,1836,2391,2510 'throwing/promise':2537 'time':1540 'toerror':2396 'togeth':732 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'tothrowingpromis':1813,1833 'track':123,2562 'transform':507,522,2406,2416 'treat':2592 'tri':216,231,246,617,640,664,1341,1359,1555 'true':459,462 'try/catch':48,76,208,596,605,1331,2568 'trycatch':437 'trycatchk':474 'ts':3,12,31,38,275,282,377,498,505,692,699,970,978,985,992,1255,1262,1412,1419,1426,1484,1492,1499,1609,1617,1703,1772,1780,2104,2112,2119,2239,2247,2254 'type':68,91,137,823,827,829,833,835,860,1002,1148,2288,2296,2310,2325,2438 'type-saf':67 'typeof':1879,1899,1913,1936 'typescript':20,73,121,144,206,268,285,370,431,491,598,685,826,900,963,1141,1248,1329,1405,1477,1554,1602,1656,1696,1765,1859,1993,2097,2232,2561 'u':1624 'u.id':1625 'ui':1197 'undefin':1065,1070,1205,1526,1628 'union':857 'unknown':444,1666,1871,1889,2007,2128 'url':1711,1718,2016,2024 'usag':1105,1958,2051,2180,2338 'use':9,22,61,473,774,824,1221,1677,2369,2506,2527,2547,2577 'user':154,163,172,176,180,184,194,230,232,241,260,296,304,308,311,316,333,560,562,616,618,624,633,667,713,723,724,751,753,757,771,795,799,801,816,819,852,868,1274,1308,1343,1348,1352,1387,1397,1436,1455,1622,1627,1632,1635,1642,1648,1691,1740,1786,1801,1831,1845,1856,2086,2089 'user.cardid':249 'user.id':1310,1366,1368,1370,1446,1449,1452 'user.isactive':631,752,800 'userid':609,620,740,749,785,797,1299,1306,1339,1346,1430,1434,2060,2065,2341,2344 'users.find':1623 'userschema':1684 'valid':95,458,461,830,891,909,954,959,1012,1015,1086,1095,2440,2462,2473,2478,2528,2602 'validateag':1062,1103 'validateemail':1022,1097,1174 'validateform':906,1091,1106,1121 'validateinput':840,870 'validatepassword':1041,1100 'validationerror':828,844,866 'validid':873,875 'valu':8,119,267,357,380,404,423,425,510,543,782,1588,1827,1829,2373,2505 'void':110 'w':854 'want':66,882 'wider':855 'without':47 'work':1595,2361 'wrap':432,1264,1657,1705,1728,2390,2533 'write':588 'wrong':1977 'x':2411,2412,2466,2467 'zoderror':1668,1676,1690 'zodschema':1664","prices":[{"id":"054a38a4-8c0b-48a8-81f1-6aeb5c1b5661","listingId":"916f40fb-4ccc-422e-b437-59d2e7202405","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:37:31.289Z"}],"sources":[{"listingId":"916f40fb-4ccc-422e-b437-59d2e7202405","source":"github","sourceId":"sickn33/antigravity-awesome-skills/fp-ts-errors","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/fp-ts-errors","isPrimary":false,"firstSeenAt":"2026-04-18T21:37:31.289Z","lastSeenAt":"2026-04-24T00:50:58.651Z"}],"details":{"listingId":"916f40fb-4ccc-422e-b437-59d2e7202405","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"fp-ts-errors","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34793,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-24T00:28:59Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"32807fb30e51dd6a7321cb2cd8f032cdf7da7999","skill_md_path":"skills/fp-ts-errors/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/fp-ts-errors"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"fp-ts-errors","description":"Handle errors as values using fp-ts Either and TaskEither for cleaner, more predictable TypeScript code. Use when implementing error handling patterns with fp-ts."},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/fp-ts-errors"},"updatedAt":"2026-04-24T00:50:58.651Z"}}