[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81573":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":13,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":15,"stars7d":16,"stars30d":17,"stars90d":13,"forks30d":13,"starsTrendScore":18,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":23,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":32,"readmeContent":33,"aiSummary":34,"trendingCount":13,"starSnapshotCount":13,"syncStatus":35,"lastSyncTime":36,"discoverSource":37},81573,"tsentials","senrecep\u002Ftsentials","senrecep","Railway-oriented programming for TypeScript — Result\u003CT>, Maybe\u003CT>, Rule Engine, and DDD base classes with full    async pipeline support","https:\u002F\u002Fsenrecep.github.io\u002Ftsentials\u002F",null,"TypeScript",39,0,24,7,10,15,21,60.5,"MIT License",false,"main",true,[25,26,27,28,29,30,31],"error-handling","functional-programming","maybe-monad","railway-oriented-programming","result-type","rule-engin","typescript","2026-06-12 04:01:34","\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Flogo-256.png\" alt=\"tsentials logo\" width=\"256\" height=\"256\" \u002F>\n\u003C\u002Fp>\n\n# tsentials\n\n[![npm version](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fv\u002Ftsentials?style=flat-square&color=blue)](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ftsentials)\n[![npm downloads](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fdm\u002Ftsentials?style=flat-square)](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ftsentials)\n[![bundle size](https:\u002F\u002Fimg.shields.io\u002Fbundlephobia\u002Fminzip\u002Ftsentials?style=flat-square&label=gzip)](https:\u002F\u002Fbundlephobia.com\u002Fpackage\u002Ftsentials)\n[![tests](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Ftests-978%20passing-brightgreen?style=flat-square)](.\u002Ftests)\n[![CI](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Factions\u002Fworkflow\u002Fstatus\u002Fsenrecep\u002Ftsentials\u002Fci.yml?branch=main&style=flat-square&label=CI)](https:\u002F\u002Fgithub.com\u002Fsenrecep\u002Ftsentials\u002Factions)\n[![license](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Flicense\u002Fsenrecep\u002Ftsentials?style=flat-square)](.\u002FLICENSE)\n[![TypeScript](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FTypeScript-5.0%2B-blue?style=flat-square&logo=typescript)](https:\u002F\u002Fwww.typescriptlang.org\u002F)\n[![Node.js](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fnode-%3E%3D18-339933?style=flat-square&logo=node.js)](https:\u002F\u002Fnodejs.org)\n\nRailway-oriented programming for TypeScript — `Result\u003CT>`, `Maybe\u003CT>`, Rule Engine, and DDD base classes with full async pipeline support.\n\n---\n\n> **[Your Function Signature Is Lying →](https:\u002F\u002Fwww.senrecep.com\u002Fen\u002Fblog\u002Fyour-function-signature-is-lying)**  \n> A deep dive into why `try\u002Fcatch` falls short in TypeScript, the philosophy behind Railway Oriented Programming, and the design decisions that shaped `tsentials`.\n\n---\n\n## Table of Contents\n\n- [Install](#install)\n- [Modules](#modules)\n- [Result\\\u003CT\\>](#resultt)\n  - [Creating Results](#creating-results)\n  - [Pipeline (sync)](#pipeline-sync)\n  - [Conditional & Guarded Pipeline](#conditional--guarded-pipeline)\n  - [Error Handling & Recovery](#error-handling--recovery)\n  - [Async Pipeline — ResultAsync\\\u003CT\\>](#async-pipeline--resultasynct)\n  - [ResultChain\\\u003CT\\> — Fluent Wrapper](#resultchaint--fluent-wrapper)\n  - [Combination & Utilities](#combination--utilities)\n- [Maybe\\\u003CT\\>](#maybet)\n  - [Creating Maybe Values](#creating-maybe-values)\n  - [Pipeline](#pipeline)\n  - [Conditional Operations](#conditional-operations)\n  - [Async Pipeline](#async-pipeline)\n  - [Collection Utilities](#collection-utilities)\n- [Result ↔ Maybe Bridge](#result--maybe-bridge)\n- [Rule Engine](#rule-engine)\n- [AppError & Err Factory](#apperror--err-factory)\n  - [Error Metadata](#error-metadata)\n- [Entity Base (DDD)](#entity-base-ddd)\n- [HTTP (fetchResult)](#http-fetchresult)\n- [Union\\\u003CT\\>](#uniont)\n- [Time & Fake Providers](#time--fake-providers)\n- [Clone Utilities](#clone-utilities)\n- [JSON Utilities](#json-utilities)\n- [pipe & flow](#pipe--flow)\n- [NonEmptyArray\\\u003CT\\>](#nonemptyarrayt)\n- [Eq\\\u003CT\\>](#eqt)\n- [Ord\\\u003CT\\>](#ordt)\n- [Predicate\\\u003CT\\>](#predicatet)\n- [These\\\u003CE, A\\>](#these-e-a)\n- [Tree\\\u003CT\\>](#treet)\n- [Record Utilities](#record-utilities)\n- [Design Notes](#design-notes)\n- [AI Skills](#ai-skills)\n\n## Install\n\n```bash\nnpm install tsentials\n```\n\n**Requirements:** Node.js ≥ 18, TypeScript ≥ 5.0\n\n## Modules\n\n| Import | Contents |\n|--------|----------|\n| `tsentials\u002Fresult` | `Result\u003CT>`, `ResultAsync\u003CT>`, `ResultChain\u003CT>`, `fromAsync`, `maybeToResult`, `resultToMaybe` |\n| `tsentials\u002Fmaybe` | `Maybe\u003CT>`, collection utilities |\n| `tsentials\u002Ferrors` | `AppError`, `ErrorType`, `Err` factory, `ErrorMetadata` |\n| `tsentials\u002Frules` | `Rule\u003CT>`, `RuleEngine` |\n| `tsentials\u002Fentity` | `createEntityBase`, `createSoftDeletable`, `DomainEvent` |\n| `tsentials\u002Fhttp` | `fetchResult`, `RequestBuilder` |\n| `tsentials\u002Ftime` | `DateTimeProvider`, `SystemDateTimeProvider`, `createFakeDateTimeProvider` |\n| `tsentials\u002Fclone` | `Cloneable\u003CT>`, `deepClone`, `cloneArray` |\n| `tsentials\u002Funion` | `Union\u003CT>` |\n| `tsentials\u002Fjson` | `Json`, `JsonObject`, `JsonArray`, `JsonPrimitive`, `safeJsonParse`, `safeJsonStringify`, `parseAndValidate`, type guards |\n| `tsentials\u002Ffunction` | `pipe`, `flow`, `identity`, `constant`, `flip` |\n| `tsentials\u002Farray` | `NonEmptyArray\u003CT>`, `head`, `tail`, `last`, `asNonEmptyArray` |\n| `tsentials\u002Feq` | `Eq\u003CT>`, `contramap`, `struct`, `getArrayEq` |\n| `tsentials\u002Ford` | `Ord\u003CT>`, `sortBy`, `min`, `max`, `clamp`, `between` |\n| `tsentials\u002Fpredicate` | `Predicate\u003CT>`, `Refinement\u003CA, B>`, `and`, `or`, `not`, `all`, `any` |\n| `tsentials\u002Fthese` | `These\u003CE, A>`, `toResult`, `fromResult`, `partition` |\n| `tsentials\u002Ftree` | `Tree\u003CT>`, `map`, `filter`, `fold`, `drawTree` |\n| `tsentials\u002Frecord` | `Record` utilities — `map`, `filter`, `pick`, `omit`, `reduce` |\n\n---\n\n## Result\\\u003CT\\>\n\nDiscriminated union `{ ok: true; value: T } | { ok: false; errors: AppError[] }`. No exceptions — errors are values.\n\n### Creating Results\n\n```typescript\nimport { Result } from 'tsentials\u002Fresult';\nimport { Err } from 'tsentials\u002Ferrors';\n\nfunction divide(a: number, b: number): Result\u003Cnumber> {\n  if (b === 0) return Result.failure(Err.validation('Math.DivideByZero', 'Cannot divide by zero'));\n  return Result.success(a \u002F b);\n}\n\n\u002F\u002F Type guards\nconst r = divide(10, 2);\nif (Result.isSuccess(r)) console.log(r.value); \u002F\u002F 5\nif (Result.isFailure(r)) console.log(Result.firstError(r).code);\n\n\u002F\u002F Conditional creation\nResult.successIf(user.age >= 18, user, Err.validation('User.Underage', 'Must be 18+'));\nResult.failIf(user.isBanned, user, Err.forbidden('User.Banned', 'Account suspended'));\n\n\u002F\u002F Wrap throwing code\nResult.try(() => JSON.parse(raw), () => Err.validation('JSON.Invalid', 'Malformed JSON'));\n\n\u002F\u002F Void success\nResult.ok();\n```\n\n### Pipeline (sync)\n\n```typescript\nimport { Result } from 'tsentials\u002Fresult';\nimport { Err } from 'tsentials\u002Ferrors';\n\nconst price = Result.success(100)\n  |> Result.map(_, n => n * 1.2)\n  |> Result.ensure(_, n => n \u003C 200, Err.validation('Price.TooHigh', 'Exceeds limit'))\n  |> Result.map(_, n => `$${n.toFixed(2)}`);\n\u002F\u002F => { ok: true, value: \"$120.00\" }\n\n\u002F\u002F Dynamic error from value\nResult.ensure(\n  Result.success(3),\n  n => n > 5,\n  n => Err.validation('Value.TooSmall', `Value ${n} is too small`),\n);\n\n\u002F\u002F Side effects\nResult.tap(price, v => console.log('computed', v));\nResult.tapError(price, errs => console.error('failed', errs[0].code));\n```\n\n### Conditional & Guarded Pipeline\n\n```typescript\nimport { Result } from 'tsentials\u002Fresult';\n\n\u002F\u002F Bind only if condition is true\nResult.bindIf(Result.success(5), true, n => Result.success(n * 2));\nResult.bindIf(Result.success(5), n => n > 3, n => Result.success(n * 2));\n\n\u002F\u002F Tap only if condition is true\nResult.tapIf(Result.success(42), true, v => console.log(v));\nResult.tapIf(Result.success(42), v => v > 10, v => console.log(v));\n\n\u002F\u002F Tap errors conditionally\nResult.tapErrorIf(\n  Result.failure(err),\n  errs => errs.length > 0,\n  errs => metrics.track(errs[0].code),\n);\n```\n\n### Error Handling & Recovery\n\n```typescript\nimport { Result } from 'tsentials\u002Fresult';\n\n\u002F\u002F Recover from all failures\nResult.compensate(Result.failure(err), () => Result.success(-1));\n\n\u002F\u002F Recover using first error only\nResult.compensateFirst(\n  Result.failureFrom([err1, err2]),\n  first => Result.success(first.code),\n);\n\n\u002F\u002F Recover only when predicate matches first error\nResult.recover(\n  Result.failure(notFoundError),\n  e => e.code === 'User.NotFound',\n  () => Result.success(guestUser),\n);\n\n\u002F\u002F Transform errors\nResult.mapError(\n  Result.failure(err),\n  errs => errs.map(e => ({ ...e, code: `Wrapped.${e.code}` })),\n);\n\n\u002F\u002F Fallback values\nResult.unwrapOr(Result.success(42), 0);       \u002F\u002F 42\nResult.unwrapOr(Result.failure(err), 0);      \u002F\u002F 0\nResult.unwrapOrElse(Result.failure(err), errs => errs.length); \u002F\u002F 1\n\n\u002F\u002F Deconstruct to tuple\nconst [ok, value, errors] = Result.deconstruct(result);\n```\n\n### Async Pipeline — ResultAsync\\\u003CT\\>\n\n`ResultAsync\u003CT>` implements `PromiseLike\u003CResult\u003CT>>` — the entire chain builds synchronously, resolves once at the end with a single `await`.\n\n```typescript\nimport { fromAsync } from 'tsentials\u002Fresult';\nimport { Err } from 'tsentials\u002Ferrors';\n\nconst profile = await fromAsync(fetchUser(userId))\n  .andThen(user => validateUser(user))\n  .ensure(user => user.isActive, Err.validation('User.Inactive', 'Not active'))\n  .map(user => user.profile)\n  .tap(p => console.log('fetched', p.name))\n  .match(\n    profile => profile,\n    () => null,\n  );\n```\n\nAsync variants of all sync operations are available: `thenAsync`, `mapAsync`, `ensureAsync`, `tapAsync`, `tapErrorAsync`, `compensateAsync`, `mapErrorAsync`.\n\n```typescript\n\u002F\u002F Conditional async bind\nawait Result.bindIfAsync(\n  Result.success(user),\n  u => u.isAdmin,\n  async u => fetchAdminDashboard(u),\n);\n\n\u002F\u002F Async recovery\nawait Result.recoverAsync(\n  Result.failure(cacheMiss),\n  e => e.code === 'Cache.Miss',\n  async () => fetchFromDatabase(),\n);\n```\n\n### ResultChain\\\u003CT\\> — Fluent Wrapper\n\n```typescript\nimport { chain } from 'tsentials\u002Fresult';\n\nconst r = chain(Result.success(5))\n  .bind(n => Result.success(n * 2))\n  .ensure(n => n > 5, Err.validation('Value.TooSmall', 'Too small'))\n  .map(n => `value: ${n}`)\n  .unwrap();\n```\n\n### Combination & Utilities\n\n```typescript\nimport { Result } from 'tsentials\u002Fresult';\n\n\u002F\u002F Collect all — succeeds only if ALL succeed\nResult.and([Result.success(1), Result.success(2)]); \u002F\u002F Result\u003C[1, 2]>\nResult.and([Result.success(1), Result.failure(err)]); \u002F\u002F collects ALL errors\n\n\u002F\u002F First success — short-circuits on first ok\nResult.or([Result.failure(err1), Result.success(99), Result.failure(err2)]);\n\n\u002F\u002F Tuple combination — preserves heterogeneous types\nResult.combine(Result.success(1), Result.success('hello'), Result.success(true));\n\u002F\u002F => Result\u003C[number, string, boolean]>\n\n\u002F\u002F Flatten nested Result\nResult.flatten(Result.success(Result.success(42))); \u002F\u002F Result\u003Cnumber>\n\n\u002F\u002F Always run cleanup regardless of outcome\nResult.always(result, r => {\n  console.log(r.ok ? 'success' : 'failure');\n  return 'done';\n});\n```\n\n---\n\n## Maybe\\\u003CT\\>\n\nExplicit optional values — no accidental `undefined`.\n\n### Creating Maybe Values\n\n```typescript\nimport { Maybe } from 'tsentials\u002Fmaybe';\n\nMaybe.some(42);\nMaybe.none\u003Cnumber>();\nMaybe.from(user.nickname);        \u002F\u002F null\u002Fundefined → None\nMaybe.fromTry(() => riskyParse()); \u002F\u002F thrown → None\n```\n\n### Pipeline\n\n```typescript\nimport { Maybe } from 'tsentials\u002Fmaybe';\n\nconst display = Maybe.getOrElse(\n  Maybe.filter(\n    Maybe.map(Maybe.from(user.nickname), s => s.trim()),\n    s => s.length > 0,\n  ),\n  () => 'Anonymous',\n);\n\n\u002F\u002F Type guards\nif (Maybe.isSome(maybe)) console.log(maybe.value);\nif (Maybe.isNone(maybe)) console.log('empty');\n\n\u002F\u002F Safe access\nMaybe.getOrUndefined(maybe);          \u002F\u002F T | undefined\nMaybe.getOrThrow(maybe, 'Missing!');  \u002F\u002F throws if None\nMaybe.deconstruct(maybe);             \u002F\u002F [true, T] | [false, undefined]\n```\n\n### Conditional Operations\n\n```typescript\nimport { Maybe } from 'tsentials\u002Fmaybe';\n\n\u002F\u002F Transform only if condition passes\nMaybe.mapIf(Maybe.some(5), true, n => n * 2);\nMaybe.mapIf(Maybe.some(5), n => n > 3, n => n * 2);\n\n\u002F\u002F Bind only if condition passes\nMaybe.bindIf(Maybe.some(5), n => n > 3, n => Maybe.some(n * 2));\n\n\u002F\u002F Fallback chain\nMaybe.or(Maybe.none\u003Cnumber>(), Maybe.some(99));        \u002F\u002F Some(99)\nMaybe.orElse(Maybe.none\u003Cnumber>(), () => Maybe.some(99)); \u002F\u002F lazy fallback\n\n\u002F\u002F Run effect when None\nMaybe.tapNone(Maybe.none\u003Cnumber>(), () => console.warn('missing'));\n```\n\n### Async Pipeline\n\n```typescript\nimport { Maybe } from 'tsentials\u002Fmaybe';\n\nconst user = await Maybe.mapAsync(Maybe.some(userId), async id => fetchUser(id));\nconst profile = await Maybe.bindAsync(user, async u =>\n  u.isActive ? Maybe.some(u.profile) : Maybe.none(),\n);\nconst filtered = await Maybe.filterAsync(profile, async p => p.isPublic);\n```\n\n### Collection Utilities\n\n```typescript\nimport { tryFirst, tryFind, choose, asMaybe } from 'tsentials\u002Fmaybe';\n\nconst first = tryFirst(items);                               \u002F\u002F Maybe\u003CT>\nconst found = tryFind(items, (x) => x.id === targetId);      \u002F\u002F Maybe\u003CT>\nconst values = choose([Maybe.some(1), Maybe.none(), Maybe.some(3)]); \u002F\u002F [1, 3]\n\nconst m = asMaybe(maybeNullValue);                           \u002F\u002F Maybe\u003CT>\n```\n\n---\n\n## Result ↔ Maybe Bridge\n\n```typescript\nimport { maybeToResult, resultToMaybe } from 'tsentials\u002Fresult';\nimport { Maybe } from 'tsentials\u002Fmaybe';\nimport { Err } from 'tsentials\u002Ferrors';\n\n\u002F\u002F Maybe → Result\nconst result = maybeToResult(Maybe.from(user), Err.notFound('User.NotFound', 'Missing'));\n\n\u002F\u002F Result → Maybe (errors dropped)\nconst maybe = resultToMaybe(Result.success(42)); \u002F\u002F Some(42)\nconst none = resultToMaybe(Result.failure(err)); \u002F\u002F None\n\n\u002F\u002F Round-trip preserves success value\nmaybeToResult(resultToMaybe(Result.success(data)), fallbackError);\n```\n\n---\n\n## Rule Engine\n\n```typescript\nimport { RuleEngine } from 'tsentials\u002Frules';\nimport type { Rule } from 'tsentials\u002Frules';\n\nconst isAdult = RuleEngine.fromPredicate\u003CUser>(\n  u => u.age >= 18,\n  Err.validation('User.Underage', 'Must be 18+'),\n);\n\n\u002F\u002F Dynamic error factory\nconst hasBalance = RuleEngine.fromPredicate\u003CAccount>(\n  a => a.balance > 0,\n  a => Err.validation('Account.Insufficient', `Balance ${a.balance} is too low`),\n);\n\n\u002F\u002F Combinators\nRuleEngine.and(isAdult, hasBalance);       \u002F\u002F ALL must pass, collects ALL errors\nRuleEngine.linear(isAdult, hasBalance);    \u002F\u002F ALL must pass, stops at first failure\nRuleEngine.or(isAdult, hasBalance);        \u002F\u002F AT LEAST ONE must pass\n\n\u002F\u002F Conditional branching\nRuleEngine.if(isAdult, hasBalance);        \u002F\u002F if adult → check balance, else skip\nRuleEngine.if(isAdult, hasBalance, minorRule); \u002F\u002F if adult → balance, else → minorRule\n\n\u002F\u002F Async rules\nconst asyncRule = RuleEngine.fromPredicateAsync\u003CUser>(\n  async u => await fetchStatus(u.id) === 'active',\n  Err.validation('User.Inactive', 'Not active'),\n);\nRuleEngine.andAsync(asyncRule, anotherAsyncRule);\nRuleEngine.linearAsync(asyncRule, anotherAsyncRule);\nRuleEngine.orAsync(asyncRule, fallbackAsyncRule);\nRuleEngine.ifAsync(asyncRule, onTrue, onFalse);\n\n\u002F\u002F Evaluation\nconst result = RuleEngine.evaluate(isAdult, user);\nconst asyncResult = await RuleEngine.evaluateAsync(asyncRule, user);\n```\n\n---\n\n## AppError & Err Factory\n\n```typescript\nimport { Err } from 'tsentials\u002Ferrors';\n\nErr.validation('Field.Required', 'Name is required');\nErr.notFound('User.NotFound', 'User does not exist');\nErr.unexpected('DB.ConnectionFailed', 'Cannot connect to database');\nErr.conflict('Email.AlreadyTaken', 'This email is already in use');\nErr.unauthorized('Auth.InvalidToken', 'Token is expired');\nErr.forbidden('Permissions.Denied', 'Insufficient permissions');\n\n\u002F\u002F From exceptions with metadata\nErr.fromException(new Error('timeout'));\nErr.fromException(new Error('timeout'), ErrorType.Unexpected, 'Network.Timeout');\n\n\u002F\u002F Structural equality\nErr.equals(errA, errB); \u002F\u002F true if code + description + type match\n```\n\n### Error Metadata\n\n```typescript\nimport { ErrorMetadata } from 'tsentials\u002Ferrors';\n\nconst meta = ErrorMetadata.fromRecord({ field: 'email', constraint: 'unique' });\nconst err = Err.validation('Email.Invalid', 'Invalid format', meta);\n\n\u002F\u002F Combine multiple metadata maps\nconst combined = ErrorMetadata.combine(baseMeta, additionalMeta);\n\n\u002F\u002F Convert back to plain object\nconst record = ErrorMetadata.toRecord(meta);\n\n\u002F\u002F Extract from exceptions\nconst exceptionMeta = ErrorMetadata.fromException(new TypeError('fail'));\n\u002F\u002F { exceptionType: 'TypeError', exceptionMessage: 'fail', exceptionStack: '...' }\n```\n\n---\n\n## Entity Base (DDD)\n\n```typescript\nimport { createEntityBase, createSoftDeletable } from 'tsentials\u002Fentity';\nimport type { DomainEvent } from 'tsentials\u002Fentity';\n\ninterface OrderCreatedEvent extends DomainEvent {\n  readonly orderId: string;\n}\n\nclass Order implements EntityBase, SoftDeletable {\n  private readonly _base = createEntityBase();\n  private readonly _softDelete = createSoftDeletable();\n\n  get domainEvents() { return this._base.domainEvents; }\n  get createdAt() { return this._base.createdAt; }\n  get createdBy() { return this._base.createdBy; }\n  get updatedAt() { return this._base.updatedAt; }\n  get updatedBy() { return this._base.updatedBy; }\n\n  get isDeleted() { return this._softDelete.isDeleted; }\n  get isHardDeleted() { return this._softDelete.isHardDeleted; }\n  get deletedAt() { return this._softDelete.deletedAt; }\n  get deletedBy() { return this._softDelete.deletedBy; }\n\n  raise(event: DomainEvent) { this._base.raise(event); }\n  clearDomainEvents() { return this._base.clearDomainEvents(); }\n  setCreatedInfo(at: Date, by: string) { this._base.setCreatedInfo(at, by); }\n  setUpdatedInfo(at: Date, by: string) { this._base.setUpdatedInfo(at, by); }\n\n  markAsDeleted(at: Date, by: string) { this._softDelete.markAsDeleted(at, by); }\n  markAsHardDeleted() { this._softDelete.markAsHardDeleted(); }\n  restore() { this._softDelete.restore(); } \u002F\u002F resets isHardDeleted too\n}\n```\n\n---\n\n## HTTP (fetchResult)\n\n`fetchResult` never throws — network errors and HTTP error responses are captured as `Result\u003CT>`.\n\n```typescript\nimport { fetchResult, RequestBuilder } from 'tsentials\u002Fhttp';\n\n\u002F\u002F Direct usage\nconst result = await fetchResult.get\u003CUser>('https:\u002F\u002Fapi.example.com\u002Fusers\u002F42');\nif (!result.ok) console.error(result.errors[0].code); \u002F\u002F 'Http.404'\n\n\u002F\u002F POST \u002F PUT \u002F PATCH \u002F DELETE\nawait fetchResult.post('\u002Fusers', { name: 'Alice' });\nawait fetchResult.put('\u002Fusers\u002F1', { name: 'Bob' });\nawait fetchResult.patch('\u002Fusers\u002F1', { active: true });\nawait fetchResult.delete('\u002Fusers\u002F1');\n\n\u002F\u002F Network errors are caught automatically\nconst r = await fetchResult.get('\u002Foffline'); \u002F\u002F Result.failure with TypeError metadata\n\n\u002F\u002F Fluent builder\nconst users = await RequestBuilder.get('https:\u002F\u002Fapi.example.com\u002Fusers')\n  .header('Authorization', `Bearer ${token}`)\n  .query('page', '1')\n  .query('limit', '10')\n  .send\u003CUser[]>();\n\n\u002F\u002F JSON body with custom headers\nconst created = await RequestBuilder.post('https:\u002F\u002Fapi.example.com\u002Fusers')\n  .header('X-Idempotency-Key', key)\n  .json({ name: 'Alice', email: 'alice@example.com' })\n  .send\u003CUser>();\n```\n\nStatus code mapping:\n\n| Status | ErrorType |\n|--------|-----------|\n| 400, 422 | `Validation` |\n| 401 | `Unauthorized` |\n| 403 | `Forbidden` |\n| 404, 410 | `NotFound` |\n| 409, 429 | `Conflict` |\n| ≥500 | `Unexpected` |\n\nSupports `application\u002Fproblem+json` (RFC 9457) for error descriptions.\n\n---\n\n## Union\\\u003CT\\>\n\nProgrammatic discriminated union with exhaustive match.\n\n```typescript\nimport { Union } from 'tsentials\u002Funion';\n\ntype PaymentResult = Union\u003C{\n  success: { transactionId: string };\n  pending: { estimatedMs: number };\n  failed: { error: AppError };\n}>;\n\nconst result = Union.of\u003C{ success: { transactionId: string }; pending: { estimatedMs: number }; failed: { error: AppError } }>('success', { transactionId: 'txn_123' });\n\nconst message = Union.match(result, {\n  success: ({ transactionId }) => `Paid! Ref: ${transactionId}`,\n  pending: ({ estimatedMs }) => `Pending for ${estimatedMs}ms`,\n  failed: ({ error }) => `Failed: ${error.description}`,\n});\n\n\u002F\u002F Type guard\nif (Union.is(result, 'success')) {\n  console.log(result.value.transactionId);\n}\n\n\u002F\u002F Unsafe extraction\nconst id = Union.get(result, 'success').transactionId; \u002F\u002F throws if wrong tag\n```\n\n---\n\n## Time & Fake Providers\n\n```typescript\nimport { SystemDateTimeProvider, createFakeDateTimeProvider } from 'tsentials\u002Ftime';\n\n\u002F\u002F Production\nconst now = SystemDateTimeProvider.utcNow();\nconst today = SystemDateTimeProvider.utcNowDate(); \u002F\u002F UTC midnight\nconst ms = SystemDateTimeProvider.utcNowMs();\n\n\u002F\u002F Testing — deterministic time\nconst fake = createFakeDateTimeProvider(new Date('2024-06-01T12:00:00Z'));\nfake.utcNow();           \u002F\u002F 2024-06-01T12:00:00Z\nfake.advance(1000);      \u002F\u002F +1 second\nfake.setTime(newDate);   \u002F\u002F jump to any time\nfake.utcNowDate();       \u002F\u002F midnight of current fake date\n```\n\n---\n\n## Clone Utilities\n\n```typescript\nimport { deepClone, cloneArray } from 'tsentials\u002Fclone';\nimport type { Cloneable } from 'tsentials\u002Fclone';\n```\n\n`deepClone` uses the native `structuredClone` API when available, falling back to a robust\nrecursive implementation. Never throws — works in React Native (Hermes) and all JS runtimes.\n\n```typescript\n\u002F\u002F Plain objects, nested structures\nconst copy = deepClone({ user: { id: 1, tags: ['a', 'b'] } });\ncopy.user.tags.push('c'); \u002F\u002F original unaffected\n\n\u002F\u002F Date, Map, Set, TypedArrays — all supported\ndeepClone({ createdAt: new Date(), lookup: new Map([['key', 'value']]) });\ndeepClone(new Uint8Array([1, 2, 3])); \u002F\u002F buffer cloned too\n\n\u002F\u002F Circular references\nconst obj: { self?: unknown } = {};\nobj.self = obj;\nconst cloned = deepClone(obj);\ncloned.self === cloned; \u002F\u002F true\n\n\u002F\u002F Error with custom properties\nconst err = Object.assign(new TypeError('fail'), { code: 'ERR_X' });\ndeepClone(err).code; \u002F\u002F 'ERR_X'\n\n\u002F\u002F Graceful degradation — never throws\ndeepClone({ fn: () => 42 });      \u002F\u002F { fn: () => 42 }  — function reference preserved\ndeepClone({ sym: Symbol('x') });  \u002F\u002F { sym: undefined } — symbols degrade to undefined\ndeepClone(new WeakMap());          \u002F\u002F WeakMap {}         — empty instance (non-iterable)\n\n\u002F\u002F Clone array of Cloneable items\nclass Product implements Cloneable\u003CProduct> {\n  constructor(public readonly id: number) {}\n  clone() { return new Product(this.id); }\n}\nconst cloned = cloneArray([new Product(1), new Product(2)]);\n```\n\n---\n\n## JSON Utilities\n\nType-safe JSON parsing and validation that returns `Result\u003CT>` — no exceptions, fits directly into the railway pipeline.\n\n```typescript\nimport { safeJsonParse, safeJsonStringify, parseAndValidate } from 'tsentials\u002Fjson';\nimport { isJsonObject } from 'tsentials\u002Fjson';\n\n\u002F\u002F Parse — returns Result\u003CJson>\nconst result = safeJsonParse('{\"name\":\"Alice\",\"age\":30}');\nif (result.ok) {\n  console.log(result.value); \u002F\u002F { name: \"Alice\", age: 30 }\n} else {\n  console.error(result.errors[0].code); \u002F\u002F \"Json.SyntaxError\" | \"Json.ValidationError\"\n}\n\n\u002F\u002F Stringify — returns Result\u003Cstring>\nconst json = safeJsonStringify({ id: 1, tags: ['a', 'b'] });\nif (json.ok) console.log(json.value); \u002F\u002F '{\"id\":1,\"tags\":[\"a\",\"b\"]}'\n\n\u002F\u002F Parse + validate with a custom type guard\ninterface User { name: string; age: number }\n\nfunction isUser(value: unknown): value is User {\n  return isJsonObject(value) && typeof value.name === 'string' && typeof value.age === 'number';\n}\n\nconst user = parseAndValidate\u003CUser>('{\"name\":\"Alice\",\"age\":30}', isUser);\nif (user.ok) console.log(user.value.name); \u002F\u002F \"Alice\" — fully typed\n```\n\n### Type Guards\n\n```typescript\nimport { isJson, isJsonObject, isJsonArray, isJsonPrimitive } from 'tsentials\u002Fjson';\n\nisJsonPrimitive('hello');        \u002F\u002F true — string | number | boolean | null\nisJsonArray([1, 2, 3]);          \u002F\u002F true\nisJsonObject({ a: 1 });          \u002F\u002F true — plain objects only, rejects Date\u002FRegExp\u002Fclass instances\nisJson({ nested: [1, null] });   \u002F\u002F true — recursive validation\nisJson({ fn: () => {} });        \u002F\u002F false — functions are not valid JSON\nisJson({ key: undefined });      \u002F\u002F false — undefined is not valid JSON\n```\n\n### Error Codes\n\n| Code | Cause |\n|------|-------|\n| `Json.SyntaxError` | `JSON.parse` failed — malformed input |\n| `Json.ValidationError` | Parsed value failed type guard |\n| `Json.StringifyFailed` | `JSON.stringify` failed (e.g. circular reference) |\n\n### Pipeline Integration\n\n```typescript\nimport { Result } from 'tsentials\u002Fresult';\nimport { safeJsonParse } from 'tsentials\u002Fjson';\n\nconst processed = Result.then(\n  safeJsonParse(rawInput),\n  data => validatePayload(data),\n);\n```\n\n---\n\n## pipe & flow\n\n```typescript\nimport { pipe, flow } from 'tsentials\u002Ffunction';\n\nconst result = pipe(\n  5,\n  n => n * 2,\n  n => n + 1,\n  n => String(n),\n); \u002F\u002F \"11\"\n\nconst doubleAndStringify = flow(\n  (n: number) => n * 2,\n  n => String(n),\n);\ndoubleAndStringify(5); \u002F\u002F \"10\"\n```\n\n## NonEmptyArray\\\u003CT\\>\n\nType-safe arrays guaranteed to have at least one element. No null checks needed for `head()` or `last()`.\n\n```typescript\nimport { NonEmptyArray, asNonEmptyArray } from 'tsentials\u002Farray';\n\nconst items: NonEmptyArray\u003Cstring> = ['a', 'b', 'c'];\nNonEmptyArray.head(items); \u002F\u002F 'a' — safe, no Maybe\nNonEmptyArray.last(items);  \u002F\u002F 'c'\n\n\u002F\u002F Safe conversion from plain array\nconst maybe = asNonEmptyArray([]);        \u002F\u002F None\nconst sure  = asNonEmptyArray([1, 2]);    \u002F\u002F Some([1, 2])\n```\n\n## Eq\\\u003CT\\> & Ord\\\u003CT\\>\n\nComposable, type-safe equality and ordering.\n\n```typescript\nimport { Eq, Ord } from 'tsentials\u002Feq';\nimport { sortBy, min, max, clamp } from 'tsentials\u002Ford';\n\ninterface User { readonly id: number; readonly name: string; }\n\nconst eqUser = Eq.struct\u003CUser>({ id: Eq.number, name: Eq.string });\n\nconst byAge = Ord.contramap(Ord.number, (u: User) => u.age);\nconst sorted = sortBy(users, byAge);\n\nmin(byAge, userA, userB);\nclamp(Ord.number, 0, 100, 150); \u002F\u002F 100\n```\n\n## Predicate\\\u003CT\\>\n\nComposable boolean predicates for validation and filtering.\n\n```typescript\nimport { Predicate } from 'tsentials\u002Fpredicate';\n\nconst isAdult = Predicate.from((u: User) => u.age >= 18);\nconst isActive = Predicate.from((u: User) => u.isActive);\n\nconst isValid = Predicate.and(isAdult, isActive);\nconst isAnyOf = Predicate.any(isAdult, isGuest, isAdmin);\n```\n\n## These\\\u003CE, A\\>\n\nPartial success — a value together with errors\u002Fwarnings. Unlike `Result\u003CT>` which is either-or, `These` allows both.\n\n```typescript\nimport { These } from 'tsentials\u002Fthese';\n\nconst parseAge = (raw: string): These\u003CAppError, number> => {\n  const age = Number(raw);\n  if (Number.isNaN(age)) return These.left(Err.validation('Age.NaN', 'Not a number'));\n  if (age \u003C 0) return These.both(Err.validation('Age.Negative', 'Negative age'), 0);\n  return These.right(age);\n};\n\nThese.toResult(parseAge('-5')); \u002F\u002F failure (Both converts to failure)\n```\n\n## Tree\\\u003CT\\>\n\nRecursive tree data structure for hierarchies.\n\n```typescript\nimport { Tree } from 'tsentials\u002Ftree';\n\nconst tree = Tree.of('root', [\n  Tree.of('a', [Tree.leaf('a1')]),\n  Tree.leaf('b'),\n]);\n\nTree.toArray(tree);           \u002F\u002F ['root', 'a', 'a1', 'b']\nTree.find(tree, v => v === 'a1');\nTree.drawTree(tree);\n```\n\n## Record Utilities\n\nFunctional operations on plain objects.\n\n```typescript\nimport { Record as R } from 'tsentials\u002Frecord';\n\nconst users = { a: { name: 'Alice' }, b: { name: 'Bob' } };\n\nR.map(users, u => u.name);            \u002F\u002F { a: 'Alice', b: 'Bob' }\nR.filter(users, u => u.name !== 'Bob');\nR.pick(users, 'a');                   \u002F\u002F { a: { name: 'Alice' } }\nR.omit(users, 'b');                   \u002F\u002F { a: { name: 'Alice' } }\n```\n\n## Design Notes\n\n- **`Result\u003CT>`** — discriminated union, no class, zero runtime overhead\n- **`ResultAsync\u003CT>`** — implements `PromiseLike\u003CResult\u003CT>>` for direct `await`; monadic bind named `andThen` to avoid thenable collision\n- **`ResultChain\u003CT>`** — fluent sync wrapper; monadic bind named `bind` (not `then`) for the same reason\n- **`Maybe\u003CT>`** — pure functional namespace, all operations are static functions\n- **`Rule\u003CT>`** — just `(ctx: T) => VoidResult`, no interface hierarchy\n- **Entity base** — mixin factory pattern (`createEntityBase()`), not abstract class inheritance\n- **`sideEffects: false`** — all subpath imports are fully tree-shakeable\n\n## AI Skills\n\nInstall skills for Claude Code, Cursor, Codex, and 50+ other AI agents:\n\n```bash\nnpx skills add senrecep\u002Ftsentials\n```\n\nEach module has a dedicated skill with accurate API examples, correct import paths, and common pitfalls.\n\n## License\n\nMIT © [Recep Şen](https:\u002F\u002Fgithub.com\u002Fsenrecep)\n","tsentials 是一个为 TypeScript 设计的面向铁路编程库，提供了 `Result\u003CT>`、`Maybe\u003CT>`、规则引擎以及支持完整异步流水线的领域驱动设计基础类。其核心功能包括错误处理、函数式编程模式支持及通过类型安全的方式来构建复杂业务逻辑的能力。它特别适合需要处理不确定输入或输出，并且希望以更优雅方式管理错误的应用场景，如后端服务开发、数据验证流程等。使用该库可以有效减少代码中的 `try\u002Fcatch` 语句，使程序更加健壮和易于维护。",2,"2026-06-11 04:05:32","CREATED_QUERY"]