
Security News
Ruby's Bundler 4.0.18 Extends Cooldown to bundle lock and bundle cache
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.
Railway-oriented programming for TypeScript — Result<T>, Maybe<T>, Rule Engine, and DDD base classes with full async pipeline support
Railway-oriented programming for TypeScript — Result<T>, Maybe<T>, Rule Engine, and DDD base classes with full async pipeline support.
npm install tsentials
Requirements: Node.js ≥ 18, TypeScript ≥ 5.0
| Import | Contents |
|---|---|
tsentials/result | Result<T>, ResultAsync<T>, ResultChain<T>, fromAsync |
tsentials/maybe | Maybe<T>, collection utilities |
tsentials/errors | AppError, ErrorType, Err factory |
tsentials/rules | Rule<T>, RuleEngine |
tsentials/entity | createEntityBase, createSoftDeletable, DomainEvent |
tsentials/http | fetchResult, RequestBuilder |
tsentials/time | DateTimeProvider, SystemDateTimeProvider, createFakeDateTimeProvider |
tsentials/clone | Cloneable<T>, deepClone, cloneArray |
tsentials/union | Union<T> |
Discriminated union { ok: true; value: T } | { ok: false; errors: AppError[] }. No exceptions — errors are values.
import { Result } from 'tsentials/result';
import { Err } from 'tsentials/errors';
function divide(a: number, b: number): Result<number> {
if (b === 0) return Result.failure(Err.validation('Math.DivideByZero', 'Cannot divide by zero'));
return Result.success(a / b);
}
const result = divide(10, 2);
if (result.ok) console.log(result.value); // 5
import { ResultChain } from 'tsentials/result';
const price = ResultChain.of(Result.success(100))
.map(n => n * 1.2)
.ensure(n => n < 200, Err.validation('Price.TooHigh', 'Price exceeds limit'))
.map(n => `$${n.toFixed(2)}`)
.match(
s => s,
() => '$0.00',
);
// => "$120.00"
ResultAsync<T> implements PromiseLike<Result<T>> — the entire chain builds synchronously, resolves once at the end with a single await.
import { fromAsync } from 'tsentials/result';
import { Err } from 'tsentials/errors';
const profile = await fromAsync(fetchUser(userId))
.andThen(user => validateUser(user))
.ensure(user => user.isActive, Err.validation('User.Inactive', 'Not active'))
.map(user => user.profile)
.tap(p => console.log('fetched', p.name))
.match(
profile => profile,
() => null,
);
Result.combine(r1, r2, r3) // Result<[T1, T2, T3]> — all-or-nothing
Result.trySync(() => JSON.parse(s)) // catches throws → Result<T>
Result.unwrapOr(result, fallback)
Result.flatten(Result.success(Result.success(42))) // Result<number>
import { Maybe } from 'tsentials/maybe';
const name = Maybe.from(user.nickname) // Some | None
const trimmed = Maybe.map(name, s => s.trim());
const filtered = Maybe.filter(trimmed, s => s.length > 0);
const display = Maybe.getOrElse(filtered, () => user.email);
const result = Maybe.match(
Maybe.bind(Maybe.from(config.timeout), ms => ms > 0 ? Maybe.some(ms) : Maybe.none()),
ms => `timeout: ${ms}ms`,
() => 'timeout: default',
);
const m = await Maybe.mapAsync(Maybe.some(userId), async id => fetchUser(id));
import { tryFirst, tryFind, choose } from 'tsentials/maybe';
const first = tryFirst(items); // Maybe<T>
const found = tryFind(items, x => x.id === targetId); // Maybe<T>
const values = choose([Maybe.some(1), Maybe.none(), Maybe.some(3)]); // [1, 3]
import { RuleEngine } from 'tsentials/rules';
import type { Rule } from 'tsentials/rules';
const isAdult: Rule<User> = ctx =>
ctx.age >= 18 ? Result.ok() : Result.failure(Err.validation('User.Underage', 'Must be 18+'));
const hasVerifiedEmail: Rule<User> = ctx =>
ctx.emailVerified ? Result.ok() : Result.failure(Err.validation('User.EmailUnverified', 'Verify email first'));
const canRegister = RuleEngine.and(isAdult, hasVerifiedEmail);
const result = await RuleEngine.evaluate(canRegister, user);
import { Err } from 'tsentials/errors';
Err.validation('Field.Required', 'Name is required')
Err.notFound('User.NotFound', 'User does not exist')
Err.unexpected('DB.ConnectionFailed', 'Cannot connect to database')
Err.conflict('Email.AlreadyTaken', 'This email is already in use')
Err.unauthorized('Auth.InvalidToken', 'Token is expired')
Err.forbidden('Permissions.Denied', 'Insufficient permissions')
Err.custom(ErrorType.Validation, 'Custom.Code', 'message', { field: 'email' })
import { createEntityBase, createSoftDeletable } from 'tsentials/entity';
const EntityBase = createEntityBase<string>();
const SoftDeletableBase = createSoftDeletable(EntityBase);
class Order extends SoftDeletableBase {
constructor(public readonly total: number) {
super({ id: crypto.randomUUID() });
}
}
const order = new Order(99.99);
order.softDelete();
console.log(order.isDeleted); // true
import { fetchResult, RequestBuilder } from 'tsentials/http';
const result = await RequestBuilder.get('https://api.example.com/users')
.header('Authorization', `Bearer ${token}`)
.query('page', '1')
.fetchResult<User[]>();
Result<T> — discriminated union, no class, zero runtime overheadResultAsync<T> — implements PromiseLike<Result<T>> for direct await; monadic bind named andThen to avoid thenable collisionResultChain<T> — fluent sync wrapper; monadic bind named bind (not then) for the same reasonMaybe<T> — pure functional namespace, all operations are static functionsRule<T> — just (ctx: T) => VoidResult, no interface hierarchycreateEntityBase()), not abstract class inheritancesideEffects: false — all subpath imports are fully tree-shakeableInstall skills for Claude Code, Cursor, Codex, and 50+ other AI agents:
npx skills add senrecep/tsentials
Each module has a dedicated skill with accurate API examples, correct import paths, and common pitfalls.
MIT © Recep Şen
FAQs
Railway-oriented programming for TypeScript — Result<T>, Maybe<T>, Rule Engine, and DDD base classes with full async pipeline support
The npm package tsentials receives a total of 290 weekly downloads. As such, tsentials popularity was classified as not popular.
We found that tsentials demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.

Company News
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.