{"_id":"@andersoncustodio/railway","name":"@andersoncustodio/railway","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@andersoncustodio/railway","version":"1.0.0","description":"Railway Oriented Programming for TypeScript","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","test":"tsx --test tests/*.test.ts","prepublishOnly":"npm run build"},"repository":{"type":"git","url":"git+https://github.com/andersoncustodio/railway.git"},"keywords":["railway","result","outcome","error-handling","typescript","rop"],"author":{"name":"Anderson Custódio","email":"npmjs@andersoncustodio.com"},"license":"MIT","devDependencies":{"tsx":"^4.21.0","typescript":"^5.4.0"},"_id":"@andersoncustodio/railway@1.0.0","gitHead":"b835537ee0eef58459f6a9e2b8b7811b657bf339","bugs":{"url":"https://github.com/andersoncustodio/railway/issues"},"homepage":"https://github.com/andersoncustodio/railway#readme","_nodeVersion":"20.18.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-MstaqFgWB3YeERtNC9Kk0urpVEMFTa6u/fYmi67mwyt0nzXQTh4j4GIJJHhxu3OESLvlEi/krLXl0GYnbhJlUw==","shasum":"873d0a21564a406d2dcff51abd401c9a2f26a73b","tarball":"https://registry.npmjs.org/@andersoncustodio/railway/-/railway-1.0.0.tgz","fileCount":17,"unpackedSize":28426,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDqCCHqcHo15WrW7vd/Y5Xd9t+7+WxZiT/qCXihXbV6pgIhAM6Ye5cQJ7NZzdEADuN6osY33Ojk4/SSHh4SR8ZuO0xX"}]},"_npmUser":{"name":"andersoncustodio","email":"contato@andersoncustodio.com"},"directories":{},"maintainers":[{"name":"andersoncustodio","email":"contato@andersoncustodio.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/railway_1.0.0_1776819687075_0.5625786447377463"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-22T01:01:26.965Z","1.0.0":"2026-04-22T01:01:27.221Z","modified":"2026-04-22T01:01:27.812Z"},"maintainers":[{"name":"andersoncustodio","email":"contato@andersoncustodio.com"}],"description":"Railway Oriented Programming for TypeScript","homepage":"https://github.com/andersoncustodio/railway#readme","keywords":["railway","result","outcome","error-handling","typescript","rop"],"repository":{"type":"git","url":"git+https://github.com/andersoncustodio/railway.git"},"author":{"name":"Anderson Custódio","email":"npmjs@andersoncustodio.com"},"bugs":{"url":"https://github.com/andersoncustodio/railway/issues"},"license":"MIT","readme":"# Railway\n\nA lightweight **Railway Oriented Programming** toolkit for TypeScript. Models operations as two parallel tracks, *success* and *failure*, so your domain code stays free of `try/catch` and error handling stays type-safe and explicit.\n\n## Install\n\n```sh\nnpm install @andersoncustodio/railway\n```\n\n## Primitives\n\n| | Purpose |\n|---|---|\n| `Result<T>` | Discriminated union (`Ok<T> \\| Err`) for **domain** logic. |\n| `Outcome<T>` | API-facing response with HTTP `status`, `data`, `meta`, `errors`. |\n| `ErrorDetail` | A single field-level error (`code`, `field`, `message`, `meta`). |\n| `ErrorCollector` | Accumulates errors from multiple validations. |\n| `HaltError` | Structured exception that carries the error track across the call stack. |\n| `HttpStatus` | Standard HTTP status codes enum. |\n\n## Value Object with `Result`\n\nA classic DDD pattern: the value object has a private constructor and a static factory returning `Result`, so invalid state is impossible to construct.\n\n```ts\nimport { Result, ErrorDetail } from '@andersoncustodio/railway';\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nexport class Email {\n  private constructor(readonly value: string) {}\n\n  static create(input: string): Result<Email> {\n    const value = input.trim();\n\n    if (value.length === 0) {\n      return Result.err([\n        ErrorDetail.from({ code: 'required', field: 'email', message: 'Email is required' }),\n      ]);\n    }\n\n    if (!EMAIL_REGEX.test(value)) {\n      return Result.err([\n        ErrorDetail.from({ code: 'invalid_format', field: 'email', message: 'Invalid email' }),\n      ]);\n    }\n\n    return Result.ok(new Email(value.toLowerCase()));\n  }\n}\n\nconst r = Email.create('foo@bar');\nif (r.isErr()) {\n  console.log(r.firstError.message); // \"Invalid email\"\n} else {\n  r.value; // Email - type is narrowed, no casting\n}\n```\n\nNo hidden control flow: the signature tells you it can fail, **and** the compiler forces you to handle it.\n\n## Aggregating errors with `ErrorCollector`\n\nValidate many value objects and report **all** errors at once instead of failing on the first.\n\n```ts\nimport { Result, ErrorCollector } from '@andersoncustodio/railway';\n\nclass User {\n  private constructor(readonly name: Name, readonly email: Email) {}\n\n  static create(params: { name: string; email: string }): Result<User> {\n    const errorCollector = ErrorCollector.create();\n\n    const name = Name.create(params.name).unwrap(errorCollector);\n    const email = Email.create(params.email).unwrap(errorCollector);\n\n    if (errorCollector.hasErrors()) return Result.err(errorCollector.errors());\n\n    return Result.ok(new User(name, email));\n  }\n}\n```\n\n`unwrap(errorCollector)` is the key: on `Err`, errors flow into the collector and the call returns `never`. Once `hasErrors()` passes, `name` and `email` are narrowed to their valid types.\n\n### Remapping field names\n\n`unwrap` accepts a second argument, a `fieldMapper`, that rewrites the `field` of each error as it flows into the collector. Useful when validating nested structures or lists, where the inner value object doesn't know its position in the parent.\n\n```ts\nclass Order {\n  private constructor(readonly items: OrderItem[]) {}\n\n  static create(params: { items: ItemInput[] }): Result<Order> {\n    const errorCollector = ErrorCollector.create();\n\n    const items = params.items.map((item, i) =>\n      OrderItem.create(item).unwrap(errorCollector, (field) => `items[${i}].${field}`)\n    );\n\n    if (errorCollector.hasErrors()) return Result.err(errorCollector.errors());\n\n    return Result.ok(new Order(items));\n  }\n}\n```\n\nAn error emitted by `OrderItem.create` with `field: 'sku'` becomes `field: 'items[2].sku'` in the final payload, giving the client a path it can use to highlight the offending input.\n\n## From domain to HTTP with `Outcome`\n\n`Outcome` wraps a domain operation for HTTP delivery, carrying the right status code, a machine-readable `code`, and a structured payload.\n\n```ts\nimport { Outcome, HttpStatus, ErrorCollector } from '@andersoncustodio/railway';\n\nasync function createUserHandler(cmd: CreateUserCommand): Promise<Outcome<{ data: { id: string } }>> {\n  const errorCollector = ErrorCollector.create();\n  const user = User.create({ name: cmd.name, email: cmd.email }).unwrap(errorCollector);\n\n  if (errorCollector.hasErrors()) {\n    return Outcome.err({\n      code: 'user.validation_error',\n      message: 'Validation failed',\n      errors: errorCollector.errors(),\n    });\n  }\n\n  const existing = await repository.findByEmail(user.email);\n  if (existing) {\n    return Outcome.err({\n      status: HttpStatus.CONFLICT,\n      code: 'user.email_taken',\n      message: 'Email already in use',\n    });\n  }\n\n  await repository.save(user);\n  return Outcome.ok({\n    status: HttpStatus.CREATED,\n    data: { id: user.id },\n  });\n}\n```\n\n`unwrap(errorCollector)` returns `User` on success; on failure, errors flow into the collector and the happy path exits through the `hasErrors()` guard. Each failure branch maps cleanly to a distinct HTTP status: `422` (default), `409`, etc. Success returns `201 Created` with the payload.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-960cba5c35ddf7a9cfc5bc30e298d54e"}