{"_id":"@aedge-io/grugway","_rev":"4-19ff43aff5c065bae57e4ed8747845a0","name":"@aedge-io/grugway","dist-tags":{"latest":"0.2.1"},"versions":{"0.0.0":{"name":"@aedge-io/grugway","version":"0.0.0","keywords":["async","clanker","clankers","clanker-friendly","either","error","errors","error-handling","fallible","functional","maybe","monad","option","result","task","typescript"],"author":{"name":"aedge-io","email":"os@aedge.io"},"license":"MIT","_id":"@aedge-io/grugway@0.0.0","maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"homepage":"https://github.com/aedge-io/grugway#readme","bugs":{"url":"https://github.com/aedge-io/grugway/issues"},"dist":{"shasum":"27a706b07c85a32814c157a9c590be2287c66ee3","tarball":"https://registry.npmjs.org/@aedge-io/grugway/-/grugway-0.0.0.tgz","fileCount":37,"integrity":"sha512-xz3a4SwJShDax9/udy4g5V7ISJXBsPIvq15wW9UqFomHiD7v0WqD8aoLv8RSINo3dlDPNjkV4Us0aWri+T1zYw==","signatures":[{"sig":"MEUCIQCnfX+9blC+heB6R/Jn55yGJFbun7gFQVSDVti76bWNNwIgaXt+e7lveGemQ1serSrim2PE9rvbdktheM5ig6b8qko=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":260878},"types":"./types/mod.d.ts","module":"./esm/mod.js","engines":{"node":">=17.0.0"},"exports":{".":{"import":{"types":"./types/mod.d.ts","default":"./esm/mod.js"}}},"gitHead":"7fe90900dc985b316696750e86025ca9afe487c0","scripts":{},"_npmUser":{"name":"raphael-paier","email":"os@aedge.io"},"repository":{"url":"git+https://github.com/aedge-io/grugway.git","type":"git"},"_npmVersion":"10.9.2","description":"Safe abstractions for fallible flows - for humans, their clankers and foes","directories":{},"_nodeVersion":"24.1.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/grugway_0.0.0_1773397899519_0.7396362639775909","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@aedge-io/grugway","version":"0.1.0","keywords":["async","clanker","clankers","clanker-friendly","either","error","errors","error-handling","fallible","functional","maybe","monad","option","result","task","typescript"],"author":{"name":"aedge-io","email":"os@aedge.io"},"license":"MIT","_id":"@aedge-io/grugway@0.1.0","maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"homepage":"https://github.com/aedge-io/grugway#readme","bugs":{"url":"https://github.com/aedge-io/grugway/issues"},"dist":{"shasum":"e11008baa71e2df37fda6ce10be693c1e0f62067","tarball":"https://registry.npmjs.org/@aedge-io/grugway/-/grugway-0.1.0.tgz","fileCount":37,"integrity":"sha512-Gp+31apG1Kew2XX2pr+6ToRQNy7+kiRn2Oy4UaXcTS1ae+Fg077QQonakNKPSNMkcBIUYWOSfXV13Z1ycuiLQw==","signatures":[{"sig":"MEQCIC0dz2kRMUzFFxLeUqBg1jJhNBXIFwiQnd2CBQzR3oOKAiARu8XuOMjWaUCjUSI1b2wzhzVS4hdkuSHu+TAMUX3KHA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aedge-io%2fgrugway@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":259588},"types":"./types/mod.d.ts","module":"./esm/mod.js","engines":{"node":">=17.0.0"},"exports":{".":{"import":{"types":"./types/mod.d.ts","default":"./esm/mod.js"}}},"gitHead":"c0349ed521f1654ec032e746ea968d833aaaa2f9","scripts":{},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4042762f-4fec-459e-894b-52cc1fe48729"}},"repository":{"url":"git+https://github.com/aedge-io/grugway.git","type":"git"},"_npmVersion":"11.9.0","description":"Safe abstractions for fallible flows - for humans, their clankers and foes","directories":{},"_nodeVersion":"24.14.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/grugway_0.1.0_1773439139537_0.4733587342703245","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aedge-io/grugway","version":"0.2.0","keywords":["async","clanker","clankers","clanker-friendly","either","error","errors","error-handling","fallible","functional","maybe","monad","option","result","task","typescript"],"author":{"name":"aedge-io","email":"os@aedge.io"},"license":"MIT","_id":"@aedge-io/grugway@0.2.0","maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"homepage":"https://github.com/aedge-io/grugway#readme","bugs":{"url":"https://github.com/aedge-io/grugway/issues"},"dist":{"shasum":"9a6be4bc802c696797ed67294322a2e96491c15d","tarball":"https://registry.npmjs.org/@aedge-io/grugway/-/grugway-0.2.0.tgz","fileCount":31,"integrity":"sha512-Pq4J1gOXaZN9XMQ/H18b043p1a8EUDKgGEaRqf8/Oc7knfQIckAyBlohonry/vVM5Sx44TpLqdl9BHIzfidTVA==","signatures":[{"sig":"MEYCIQCOoT+iYAV/V+uMKk6R+nY1vZdz7e9q2hm26Jx9gQ3g6gIhAJ7mJlRlUmdqbvT2SwcZkLm3wWFOqFsfWlsJ1VyDVHVr","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aedge-io%2fgrugway@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":252464},"types":"./types/mod.d.ts","module":"./esm/mod.js","engines":{"node":">=17.0.0"},"exports":{".":{"import":{"types":"./types/mod.d.ts","default":"./esm/mod.js"}}},"gitHead":"b4c7f07784b93da6e526a0bd58b0d5ae586c524e","scripts":{},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4042762f-4fec-459e-894b-52cc1fe48729"}},"repository":{"url":"git+https://github.com/aedge-io/grugway.git","type":"git"},"_npmVersion":"11.9.0","description":"Safe abstractions for fallible flows - for humans, their clankers and foes","directories":{},"_nodeVersion":"24.14.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/grugway_0.2.0_1773705974796_0.6176347473863073","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aedge-io/grugway","version":"0.2.1","description":"Safe abstractions for fallible flows - for humans, their clankers and foes","keywords":["async","clanker","clankers","clanker-friendly","either","error","errors","error-handling","fallible","functional","maybe","monad","option","result","task","typescript"],"author":{"name":"aedge-io","email":"os@aedge.io"},"repository":{"type":"git","url":"git+https://github.com/aedge-io/grugway.git"},"license":"MIT","bugs":{"url":"https://github.com/aedge-io/grugway/issues"},"module":"./esm/mod.js","types":"./types/mod.d.ts","exports":{".":{"import":{"types":"./types/mod.d.ts","default":"./esm/mod.js"}}},"scripts":{},"engines":{"node":">=17.0.0"},"publishConfig":{"access":"public","provenance":true},"gitHead":"ee45ba7cb316f42b8b59a98a9db54a2f80ec1354","_id":"@aedge-io/grugway@0.2.1","homepage":"https://github.com/aedge-io/grugway#readme","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-U20CGj5c6lcW+pfY/NfUGM8z4p7ekpb1Q9hdpEfFRmV9viRpBhqFu4SXr3RlH9VoBngHFkobgjOcONb1HAGZ5Q==","shasum":"97fcd8cea4f87c6fd8d6125bd7c392e53642a79b","tarball":"https://registry.npmjs.org/@aedge-io/grugway/-/grugway-0.2.1.tgz","fileCount":37,"unpackedSize":221987,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aedge-io%2fgrugway@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGrdBaIzK7e0WBCWhet1A3cvaDr2dULN2BnbnitUumOTAiBcZZOJO7nU9wd3uS8kpOSdNhZ9M5/cmtYu+DfJtCNXkw=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4042762f-4fec-459e-894b-52cc1fe48729"}},"directories":{},"maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/grugway_0.2.1_1773786293412_0.8541615482851643"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-13T10:31:39.394Z","modified":"2026-03-17T22:24:53.905Z","0.0.0":"2026-03-13T10:31:39.666Z","0.1.0":"2026-03-13T21:58:59.712Z","0.2.0":"2026-03-17T00:06:14.997Z","0.2.1":"2026-03-17T22:24:53.582Z"},"bugs":{"url":"https://github.com/aedge-io/grugway/issues"},"author":{"name":"aedge-io","email":"os@aedge.io"},"license":"MIT","homepage":"https://github.com/aedge-io/grugway#readme","keywords":["async","clanker","clankers","clanker-friendly","either","error","errors","error-handling","fallible","functional","maybe","monad","option","result","task","typescript"],"repository":{"type":"git","url":"git+https://github.com/aedge-io/grugway.git"},"description":"Safe abstractions for fallible flows - for humans, their clankers and foes","maintainers":[{"name":"raphael-paier","email":"os@aedge.io"}],"readme":"# grugway\n\n[![codecov](https://codecov.io/github/aedge-io/grugway/graph/badge.svg?token=9WTDQ8WOKW)](https://codecov.io/github/aedge-io/grugway)\n![NPM Version](https://img.shields.io/npm/v/%40aedge-io%2Fgrugway)\n![JSR Version](https://img.shields.io/jsr/v/%40aedge-io/grugway)\n\n> Safe abstractions for fallible flows — for humans, their clankers and foes.\n\n---\n\n## Why grugway?\n\n- **Explicit error handling** makes code behavior predictable for both humans\n  and agents\n- **Composable abstractions** allow agents to reason about data flow without\n  hidden exceptions\n- **Type-safe operations** catch mistakes at compile time rather than runtime\n- **Callibrated documentation** provides context that agents can leverage for\n  better performance (see [`SKILL.md`](SKILL.md))\n\n---\n\n## Core Abstractions\n\n| Abstraction    | Purpose                               | Equivalent              |\n| -------------- | ------------------------------------- | ----------------------- |\n| `Option<T>`    | Handle presence/absence of values     | `T \\| undefined`        |\n| `Result<T, E>` | Handle success/failure explicitly     | `T \\| E`                |\n| `Task<T, E>`   | Async operations with explicit errors | `Promise<Result<T, E>>` |\n\n---\n\n## Quick Start\n\n### Installation\n\n**Node.js / Bun:**\n\n```bash\n(bun | (p)npm) add @aedge-io/grugway\n```\n\n**Deno**:\n\n```bash\ndeno add jsr:@aedge-io/grugway\n```\n\n### Usage\n\n```typescript\nimport { Err, None, Ok, Option, Result, Some, Task } from \"@aedge-io/grugway\";\n```\n\n### Runtime Requirements\n\n- **Bun:** ≥1.0.0\n- **Deno:** ≥1.14\n- **Node.js:** ≥17.0.0\n- **Browsers:** Support for `Error.cause` and `structuredClone`\n\n---\n\n## Examples\n\n### Option — Handling Optional Values\n\n```typescript\nimport { None, Option, Some } from \"@aedge-io/grugway\";\nimport { getUserById, id } from \"grugway/examples\";\nimport type { User } from \"grugway/examples\";\n\n// Nullish values become None\nconst maybeUser = Option(getUserById(id)); // Option<User>\n\n// Chain operations safely\nconst email = maybeUser\n  .filter((user: User) => user.isActive)\n  .map((user: User) => user.email)\n  .unwrapOr(\"no-email@example.com\");\n\n// Convert to Result for error handling\nconst userResult = maybeUser.okOrElse(() => new Error(\"User not found\"));\n```\n\n### Result — Explicit Error Handling\n\n```typescript\nimport { Err, Ok, Result } from \"@aedge-io/grugway\";\n\nfunction divide(a: number, b: number): Result<number, Error> {\n  if (b === 0) return Err(new Error(\"Division by zero\"));\n  return Ok(a / b);\n}\n\n// Compose operations\nconst result = divide(10, 2)\n  .map((n) => n * 2) // Only runs on Ok\n  .andThen((n) => divide(n, 3)) // Chain fallible operations\n  .mapErr((e) => new TypeError(e.message)); // Transform errors\n\n// Unwrap with type narrowing\nif (result.isOk()) {\n  console.log(result.unwrap()); // this is inferred as number\n}\n```\n\n### Task — Async Operations\n\n```typescript\nimport { Err, Ok, Task } from \"@aedge-io/grugway\";\nimport { validateName } from \"grugway/examples\";\n\n// Create tasks from promises\nconst fetchUser = Task.fromPromise(\n  fetch(\"/api/user\").then((r) => r.json()),\n  (e: unknown) => new Error(\"Failed to fetch user\", { cause: e }),\n);\n\n// Compose async operations with the same API as Result\nconst userName = fetchUser\n  .map((user: { name: string }) => user.name)\n  .andThen(validateName)\n  .mapErr((e) => ({ code: \"USER_ERROR\", message: e.message }));\n\n// Tasks are awaitable\nconst result = await userName;\nresult.inspect(console.log).inspectErr(console.error);\n```\n\n### Lifting External Code\n\nIntegrate third-party libraries without manual wrapping:\n\n```typescript\nimport { Option, Result, Task } from \"@aedge-io/grugway\";\nimport * as semver from \"@std/semver\";\n\n// Lift sync functions\nconst tryParse = Result.liftFallible(\n  semver.parse,\n  (e: unknown) => new TypeError(\"Invalid version\", { cause: e }),\n);\n\n// Lift async functions\nconst tryFetch = Task.liftFallible(\n  (url: string) => fetch(url).then((r) => r.json()),\n  (e: unknown) => new Error(\"Fetch failed\", { cause: e }),\n);\n\n// Use in pipelines\nconst version = Option(Deno.args[0])\n  .okOr(new Error(\"No version provided\"))\n  .andThen(tryParse);\n```\n\nCheckout the [examples](examples/adapters/web/) to see how to do this more\ngranuarly.\n\n---\n\n## API Reference: Constructors & Helpers\n\nEach abstraction provides multiple constructors and composability helpers for\ndifferent scenarios.\n\n### Option<T>\n\n#### Constructors\n\n| Constructor                   | Returns       | Use When                                   |\n| ----------------------------- | ------------- | ------------------------------------------ |\n| `Option(value)`               | `Option<T>`   | General use — `null`/`undefined` → `None`  |\n| `Option.from(value)`          | `Option<T>`   | Alias for `Option()`                       |\n| `Option.fromCoercible(value)` | `Option<T>`   | Falsy values (`0`, `\"\"`, `false`) → `None` |\n| `Option.fromFallible(value)`  | `Option<T>`   | `Error` instances → `None`                 |\n| `Some(value)`                 | `Some<T>`     | Explicitly wrap a non-nullish value        |\n| `None`                        | `None`        | The absent value singleton                 |\n| `Some.empty()`                | `Some<Empty>` | Signal success without a meaningful value  |\n\n```typescript\nimport { Option } from \"@aedge-io/grugway\";\n\n// Choose based on what should be \"absent\"\nOption(0); // Some(0) — zero is a valid number\nOption.fromCoercible(0); // None   — zero is \"empty\" in this context\n\nOption(new Error()); // Some(Error) — errors are values too\nOption.fromFallible(new Error()); // None    — errors mean absence\n```\n\n#### Composability Helpers\n\n| Helper                           | Purpose                                                          |\n| -------------------------------- | ---------------------------------------------------------------- |\n| `Option.lift(fn, ctor?)`         | Wrap a function to return `Option` (default ctor: `Option.from`) |\n| `Option.liftFallible(fn, ctor?)` | Same as `lift`, but exceptions → `None`                          |\n| `Option.apply(fn, arg)`          | Apply `Option<Fn>` to `Option<Arg>` (applicative)                |\n| `Option.id(opt)`                 | Identity — useful for flattening `Option<Option<T>>`             |\n\n```typescript\nimport { Option } from \"@aedge-io/grugway\";\n\n// Lift a parser that might return undefined\nconst parseIntSafe = Option.lift(parseInt, Option.fromCoercible);\nparseIntSafe(\"42\"); // Some(42)\nparseIntSafe(\"abc\"); // None (NaN is falsy)\n\n// Lift a function that throws\nconst parseJSON = Option.liftFallible(JSON.parse);\nparseJSON('{\"a\":1}'); // Some({a: 1})\nparseJSON(\"invalid\"); // None\n```\n\n#### Collection Helpers (`Options` namespace)\n\n| Helper                  | Returns       | Behavior                                      |\n| ----------------------- | ------------- | --------------------------------------------- |\n| `Options.all(opts)`     | `Option<T[]>` | All `Some` → `Some<T[]>`, any `None` → `None` |\n| `Options.any(opts)`     | `Option<T>`   | First `Some` found, or `None`                 |\n| `Options.areSome(opts)` | `boolean`     | Type predicate: all are `Some`                |\n| `Options.areNone(opts)` | `boolean`     | Type predicate: all are `None`                |\n\n---\n\n### Result<T, E>\n\n#### Constructors\n\n| Constructor                         | Returns            | Use When                                              |\n| ----------------------------------- | ------------------ | ----------------------------------------------------- |\n| `Ok(value)`                         | `Ok<T>`            | Explicit success                                      |\n| `Err(error)`                        | `Err<E>`           | Explicit failure                                      |\n| `Result(value)`                     | `Result<T, E>`     | Auto-detect — `Error` instances → `Err`               |\n| `Result.from(fn)`                   | `Result<T, never>` | Get value of infallible function (throws → propagate) |\n| `Result.fromFallible(fn, errMapFn)` | `Result<T, E>`     | Get value of fallible function (throws → `Err`)       |\n| `Ok.empty()`                        | `Ok<Empty>`        | Signal success without a value                        |\n| `Err.empty()`                       | `Err<Empty>`       | Signal failure without details                        |\n\n```typescript\nimport { Result } from \"@aedge-io/grugway\";\nimport { getString, MathError, riskyDivision } from \"grugway/examples\";\n\n// Auto-detection for union types\nconst value: string | TypeError = getString();\nResult(value); // Ok<string> or Err<TypeError> based on runtime type\n\n// Get the result of a fallible function\nconst safeDivide = Result.fromFallible(\n  riskyDivision,\n  (e: unknown) => new MathError(\"Division failed\", { cause: e }),\n);\n```\n\n#### Composability Helpers\n\n| Helper                                     | Purpose                                                             |\n| ------------------------------------------ | ------------------------------------------------------------------- |\n| `Result.lift(fn, ctor?)`                   | Wrap function to return `Result` (panics propagate)                 |\n| `Result.liftFallible(fn, errMapFn, ctor?)` | Wrap function, map exceptions to `Err<E>`                           |\n| `asInfallible`                             | Error mapper that re-throws — marks function as \"should never fail\" |\n\n```typescript\nimport { asInfallible, Ok, Result } from \"@aedge-io/grugway\";\n// Integrate a library function that throws\nimport * as semver from \"@std/semver\";\n\nconst tryParse = Result.liftFallible(\n  semver.parse,\n  (e: unknown) => new TypeError(\"Invalid semver\", { cause: e }),\n);\n\nOk(\"1.2.3\").andThen(tryParse); // Ok<SemVer>\nOk(\"bad\").andThen(tryParse); // Err<TypeError>\n\n// Mark a function as infallible (will throw Panic if it actually fails)\nconst alwaysParses = Result.liftFallible(\n  (input: string) => JSON.parse(input),\n  asInfallible, // \"I promise this won't throw\"\n);\n```\n\n#### Collection Helpers (`Results` namespace)\n\n| Helper                 | Returns          | Behavior                                         |\n| ---------------------- | ---------------- | ------------------------------------------------ |\n| `Results.all(results)` | `Result<T[], E>` | All `Ok` → `Ok<T[]>`, first `Err` short-circuits |\n| `Results.any(results)` | `Result<T, E[]>` | First `Ok` found, or all `Err`s collected        |\n\n```typescript\nimport { Results } from \"@aedge-io/grugway\";\nimport {\n  input,\n  loadDefaults,\n  loadFromEnv,\n  loadFromFile,\n  validateAge,\n  validateEmail,\n  validateName,\n} from \"grugway/examples\";\n\n// Validate multiple fields, fail on first error. Supports tuples!\nconst validated = Results.all(\n  [\n    validateName(input.name),\n    validateEmail(input.email),\n    validateAge(input.age),\n  ] as const,\n);\n// Result<[string, string, number], ValidationError>\n\n// Try multiple strategies, succeed on first\nconst config = Results.any([\n  loadFromEnv(),\n  loadFromFile(),\n  loadDefaults(),\n]);\n// Result<Config, EnvError|FileError|DefaultError[]>\n```\n\n---\n\n### Task<T, E>\n\n#### Constructors\n\n| Constructor                           | Returns              | Use When                                              |\n| ------------------------------------- | -------------------- | ----------------------------------------------------- |\n| `Task.succeed(value)`                 | `Task<T, never>`     | Immediate success                                     |\n| `Task.fail(error)`                    | `Task<never, E>`     | Immediate failure                                     |\n| `Task.of(result)`                     | `Task<T, E>`         | From `Result<T,E>` or `Promise<Result<T,E>>`          |\n| `Task.from(fn)`                       | `Task<T, E>`         | From function returning `Result` or `Promise<Result>` |\n| `Task.fromPromise(promise, errMapFn)` | `Task<T, E>`         | From `Promise<T>`, map rejections to `Err`            |\n| `Task.fromFallible(fn, errMapFn)`     | `Task<T, E>`         | From async function that might throw                  |\n| `Task.deferred()`                     | `DeferredTask<T, E>` | For push-based APIs (callbacks, events)               |\n\n```typescript\nimport { Task } from \"@aedge-io/grugway\";\nimport { Data, FetchError, legacyApi, TimeoutError } from \"grugway/examples\";\n\n// Wrap fetch with proper error handling\nconst fetchJson = <T>(url: string): Task<T, FetchError> =>\n  Task.fromPromise(\n    fetch(url).then((r) => r.json()),\n    (e: unknown) => new FetchError(url, { cause: e }),\n  );\n\n// Deferred task for callback or push based APIs\nconst { task, succeed, fail } = Task.deferred<Data, TimeoutError>();\nconst timer = setTimeout(() => fail(new TimeoutError()), 5000);\nlegacyApi.fetch((err, data) => err ? fail(err) : succeed(data!));\nawait task; // Resolves when either callback fires\nclearTimeout(timer);\n```\n\n#### Composability Helpers\n\n| Helper                                   | Purpose                                         |\n| ---------------------------------------- | ----------------------------------------------- |\n| `Task.liftFallible(fn, errMapFn, ctor?)` | Wrap async function, map exceptions to `Err<E>` |\n\n```typescript\nimport { Task } from \"@aedge-io/grugway\";\nimport { ApiError } from \"grugway/examples\";\n\n// Lift an async library function\n// Check out the ready-made fetch adapter example for a more thorough take on this\nconst tryFetch = Task.liftFallible(\n  async (url: string) => {\n    const res = await fetch(url);\n    if (!res.ok) throw new Error(`HTTP ${res.status}`);\n    return res.json();\n  },\n  (e: unknown) => new ApiError(\"Request failed\", { cause: e }),\n);\n\n// Use in pipelines\nTask.succeed(\"/api/users\")\n  .andThen(tryFetch)\n  .map((users: { active: boolean }[]) => users.filter((u) => u.active))\n  .inspectErr(console.error);\n```\n\n#### Collection Helpers (`Tasks` namespace)\n\n| Helper             | Returns        | Behavior                                              |\n| ------------------ | -------------- | ----------------------------------------------------- |\n| `Tasks.all(tasks)` | `Task<T[], E>` | All succeed → `Ok<T[]>`, first failure short-circuits |\n| `Tasks.any(tasks)` | `Task<T, E[]>` | First success, or all failures collected              |\n\n```typescript\nimport { Tasks } from \"@aedge-io/grugway\";\nimport {\n  fetchFromCache,\n  fetchFromPrimary,\n  fetchFromReplica,\n  fetchOrders,\n  fetchProducts,\n  fetchUsers,\n} from \"grugway/examples\";\n\n// These work for all iterables\n// Parallel fetch with combined results\nconst allData = await Tasks.all(\n  [\n    fetchUsers(),\n    fetchProducts(),\n    fetchOrders(),\n  ] as const,\n);\n// Result<[User[], Product[], Order[]], ApiError>\n\n// Race multiple sources\nconst fastestResponse = await Tasks.any(\n  [\n    fetchFromPrimary(),\n    fetchFromReplica(),\n    fetchFromCache(),\n  ] as const,\n);\n// Result<Data, [PrimaryError, ReplicaError, CacheError]>\n```\n\n---\n\n## Key Patterns\n\n### Railway-Oriented Programming\n\nBuild pipelines where success flows forward and errors short-circuit:\n\n```typescript\nimport { Task } from \"@aedge-io/grugway\";\nimport {\n  generateReceipt,\n  getOrder,\n  logError,\n  processPayment,\n  validateOrder,\n} from \"grugway/examples\";\nimport type { OrderError, Receipt } from \"grugway/examples\";\n\nfunction processOrder(orderId: string): Task<Receipt, OrderError> {\n  return getOrder(orderId) // Task<Order, NotFoundError>\n    .andThen(validateOrder) // Task<Order, ValidationError>\n    .andThen(processPayment) // Task<Payment, PaymentError>\n    .andThen(generateReceipt) // Task<Receipt, ReceiptError>\n    .inspectErr(logError);\n}\n```\n\n### Pass-through Conditionals\n\nValidate without consuming the value:\n\n```typescript\nimport { Result } from \"@aedge-io/grugway\";\nimport { isValid, isWritable, parse, writeFile } from \"grugway/examples\";\n\nfunction saveFile(path: string): Result<void, Error> {\n  return parse(path)\n    .andEnsure(isValid) // Validate, but keep original path\n    .andEnsure(isWritable) // Check permissions, keep path\n    .andThen(writeFile);\n}\n```\n\n---\n\n## Best Practices\n\n1. **Computations, not data** — Use these abstractions for operation results,not\n   data models\n2. **Embrace immutability** — Don't mutate wrapped values\n3. **Unwrap at the edges** — Keep Result/Task types in your domain logic; unwrap\n   at API boundaries\n4. **Some errors are fatal** — It's okay to throw for truly unrecoverable\n   states. Just make sure to catch at the top level and terminate gracefully.\n5. **Lift external code** — Use `liftFallible` to integrate libraries cleanly\n\n---\n\n## Performance\n\nThese abstractions are _not totally performance prohibitive_. In benchmarks, the\nlinear return path often performs slightly better than nested try/catch blocks:\n\n```\nSynchronous:  Result flow ~1.3x faster than exceptions\nAsynchronous: Task flow   ~1.0x (equivalent performance)\n```\n\n**Your mileage will vary though.** Memory isn't free. Run benchmarks yourself:\n`deno bench`\n\n### License\n\nThis is a ~~fork~~ rework of an old, personal project\n[eitherway](https://github.com/realpha/eitherway).\n\nMIT License — see [LICENSE.md](./LICENSE.md)\n\n- Original eitherway: Copyright © 2023-2025 realpha\n- grugway modifications: Copyright © 2026 aedge-io\n\n---\n\n## Resources\n\n- [Original eitherway documentation](https://deno.land/x/eitherway)\n- [\"Railway-oriented programming\" — Scott Wlaschin](https://vimeo.com/113707214)\n- [\"Boundaries\" — Gary Bernhardt](https://www.destroyallsoftware.com/talks/boundaries)\n- [\"Errors are values\" — Rob Pike](https://go.dev/blog/errors-are-values)\n","readmeFilename":"README.md"}