
Research
/Security News
Critical Vulnerability in NestJS Devtools: Localhost RCE via Sandbox Escape
A flawed sandbox in @nestjs/devtools-integration lets attackers run code on your machine via CSRF, leading to full Remote Code Execution (RCE).
@zerothrow/core
Advanced tools
Core ZeroThrow functionality - Rust-style Result<T,E> for TypeScript
๐ง ZeroThrow Layers
โข ZT โ primitives (try
,tryAsync
,ok
,err
)
โข Result โ combinators (map
,andThen
,orElse
)
โข ZeroThrow โ utilities (collect
,firstSuccess
,pipe
)
โข @zerothrow/* โ ecosystem packages (resilience, jest, etc)
ZeroThrow Ecosystem ยท Packages โข
Core ZeroThrow functionality - Rust-style Result<T, E>
for TypeScript. Type-safe error handling with zero runtime dependencies (ZeroThrow itself has none).
npm install @zerothrow/core
# or: pnpm add @zerothrow/core
For production applications, we strongly recommend installing these companion packages:
# Add resilience patterns (retry, circuit breaker, timeout)
npm install @zerothrow/resilience
# Add test matchers for your test framework
npm install --save-dev @zerothrow/jest # For Jest
npm install --save-dev @zerothrow/vitest # For Vitest
Note: The packages marked --save-dev
assume @zerothrow/core
is already installed.
Why these packages?
Exceptions break the flow of your program and hide error paths. Results make errors explicit:
// โ Throws: Hidden error paths, unclear what can fail
function divide(a: number, b: number): number {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
// โ
Results: Explicit error handling, clear contract
function divide(a: number, b: number): Result<number, Error> {
if (b === 0) return ZT.err('DIV_BY_ZERO', 'Cannot divide by zero');
return ZT.ok(a / b);
}
Performance: Returning objects is ~93ร faster than throwing/catching on error paths. Creating Results on the happy path is allocation-free in modern JS engines.
ZT.try
at absolute boundaries - When interfacing with code you don't controlimport { ZT, Result } from '@zerothrow/core';
// โ
Write Result-first functions
function parseConfig(input: string): Result<Config, Error> {
if (!input) {
return ZT.err('CONFIG_EMPTY', 'Configuration cannot be empty');
}
// Only use try for code you don't control (JSON.parse)
return ZT.try(() => JSON.parse(input))
.andThen(data => {
if (!data.version) {
return ZT.err('CONFIG_INVALID', 'Missing version field');
}
return ZT.ok(data as Config);
});
}
// Chain operations cleanly
const config = parseConfig(rawInput)
.map(cfg => ({ ...cfg, timestamp: Date.now() }))
.tap(cfg => console.log('Loaded config:', cfg.version))
.unwrapOr(defaultConfig);
For the complete API documentation, see Full API Reference โ
import { ZT } from '@zerothrow/core';
// Wrap throwing functions
ZT.try(() => risky()) // Result<T, ZeroError>
ZT.tryAsync(async () => fetch()) // Promise<Result<T, ZeroError>>
// Create Results directly
ZT.ok(value) // Result<T, never>
ZT.err(error) // Result<never, Error>
ZT.err('CODE') // Result<never, ZeroError>
ZT.err('CODE', 'message') // Result<never, ZeroError>
Results are optimized for real-world use:
/bench
)Run benchmarks yourself: npm run bench
in the project root.
Your functions should return Results from the start:
// โ Bad: Throwing inside your own functions
function getUser(id: string): User {
const user = db.find(id);
if (!user) throw new Error('User not found');
return user;
}
// โ
Good: Return Results directly
function getUser(id: string): Result<User, Error> {
const user = db.find(id);
if (!user) {
return ZT.err('USER_NOT_FOUND', `No user with id ${id}`);
}
return ZT.ok(user);
}
// Usage is explicit about error handling
const userName = getUser('123')
.map(user => user.name)
.unwrapOr('Guest');
ZT.try
is ONLY for code you don't control that might throw:
// โ
Good: Only wrap third-party code that throws
function loadUserData(jsonString: string): Result<UserData, Error> {
// JSON.parse can throw - we don't control it
return ZT.try(() => JSON.parse(jsonString))
.andThen(data => {
// Our validation returns Results, no try needed!
return validateUserData(data);
});
}
// โ Bad: Wrapping your own code in try
function calculateTotal(items: Item[]): Result<number, Error> {
return ZT.try(() => {
// Why are you wrapping your own logic?
return items.reduce((sum, item) => sum + item.price, 0);
});
}
// โ
Good: Return Results directly from your functions
function calculateTotal(items: Item[]): Result<number, Error> {
if (items.length === 0) {
return ZT.err('EMPTY_CART', 'Cannot calculate total for empty cart');
}
const total = items.reduce((sum, item) => sum + item.price, 0);
return ZT.ok(total);
}
Chain operations without nested if statements:
// โ Imperative style
const userResult = await fetchUser(id);
if (!userResult.ok) {
return userResult;
}
const validationResult = validateUser(userResult.value);
if (!validationResult.ok) {
return validationResult;
}
return ZT.ok(validationResult.value);
// โ
Functional style
return fetchUser(id)
.andThen(user => validateUser(user));
Use ZeroError for rich, type-safe errors:
import { ZT, ZeroError, type Result } from '@zerothrow/core';
// Define your error codes
type AppErrorCode = 'VALIDATION_ERROR' | 'NOT_FOUND' | 'UNAUTHORIZED';
// Create typed errors with context
function createError<T extends Record<string, unknown>>(
code: AppErrorCode,
message: string,
context?: T
) {
return new ZeroError(code, message, { context });
}
// Use in your functions
function findUser(id: string): Result<User, ZeroError> {
const user = db.users.get(id);
if (!user) {
return ZT.err(createError(
'NOT_FOUND',
`User ${id} not found`,
{ userId: id, timestamp: Date.now() }
));
}
return ZT.ok(user);
}
Write async functions that return Results:
// โ Bad: Using tryAsync to wrap throwing code
async function fetchUser(id: string) {
return ZT.tryAsync(async () => {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('Failed');
return response.json();
});
}
// โ
Good: Return Results directly
async function fetchUser(id: string): Promise<Result<User, Error>> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
return ZT.err('FETCH_FAILED', `HTTP ${response.status}`);
}
// Only use try for the throwing json() method
return ZT.try(() => response.json());
}
// Even better with resilience
import { Policy } from '@zerothrow/resilience';
const resilientFetch = Policy.retry(3).execute(
() => fetchUser('123')
);
import { ZT, ZeroThrow, type Result, ZeroError } from '@zerothrow/core';
function validateEmail(email: string): Result<string, ZeroError> {
if (!email.includes('@')) {
return ZT.err('INVALID_EMAIL', 'Email must contain @');
}
return ZT.ok(email.toLowerCase());
}
function validateAge(age: number): Result<number, ZeroError> {
if (age < 0 || age > 150) {
return ZT.err('INVALID_AGE', 'Age must be between 0 and 150');
}
return ZT.ok(age);
}
// Combine validations (use type assertion for mixed types)
const validatedData = ZeroThrow.collect([
validateEmail(input.email),
validateAge(input.age)
] as const);
// Validation with error accumulation
function validateUser(data: unknown): Result<ValidUser, ZeroError> {
const errors: string[] = [];
if (!data.name) errors.push('Name is required');
if (!data.email?.includes('@')) errors.push('Invalid email');
if (data.age < 18) errors.push('Must be 18 or older');
if (errors.length > 0) {
return ZT.err(
'VALIDATION_FAILED',
errors.join(', ')
);
}
return ZT.ok(data as ValidUser);
}
// โ
Result-first API calls
async function apiCall<T>(endpoint: string): Promise<Result<T, Error>> {
const response = await fetch(endpoint);
if (!response.ok) {
return ZT.err(
'API_ERROR',
`Request failed: ${response.status} ${response.statusText}`
);
}
// Only wrap the part that actually throws
return ZT.try(() => response.json() as Promise<T>);
}
// Use it with proper error handling
const activeUsers = await apiCall<User[]>('/api/users')
.then(r => r
.map(users => users.filter(u => u.active))
.tapErr(err => console.error(`Failed to fetch users: ${err.message}`))
.unwrapOr([])
);
// โ
Each loader returns a Result
function loadFromEnv(): Result<Config, Error> {
const configStr = process.env.CONFIG;
if (!configStr) {
return ZT.err('NO_ENV_CONFIG', 'CONFIG environment variable not set');
}
return ZT.try(() => JSON.parse(configStr))
.mapErr(e => new ZeroError('INVALID_ENV_CONFIG', e.message));
}
function loadFromFile(path: string): Result<Config, Error> {
if (!fs.existsSync(path)) {
return ZT.err('CONFIG_NOT_FOUND', `No config file at ${path}`);
}
return ZT.try(() => JSON.parse(fs.readFileSync(path, 'utf8')))
.mapErr(e => new ZeroError('INVALID_FILE_CONFIG', e.message));
}
// Try multiple sources
const config = ZeroThrow.firstSuccess([
() => loadFromEnv(),
() => loadFromFile('./config.json'),
() => ZT.ok(defaultConfig) // Final fallback
]);
For comprehensive documentation including:
See the Full API Documentation โ
ZeroThrow follows these core principles:
throw
with Result<T, E>
// Before
try {
const data = JSON.parse(input);
const user = await fetchUser(data.id);
return process(user);
} catch (error) {
logger.error('Failed', error);
return null;
}
// After
ZT.try(() => JSON.parse(input))
.andThen(data => fetchUser(data.id))
.andThen(user => process(user))
.tapErr(error => logger.error('Failed', error))
.unwrapOr(null);
// Before
fetchData()
.then(data => transform(data))
.then(result => save(result))
.catch(error => {
console.error(error);
return fallback;
});
// After
ZT.tryAsync(() => fetchData())
.then(r => r
.andThen(data => transform(data))
.andThen(result => save(result))
.tapErr(error => console.error(error))
.unwrapOr(fallback)
);
(Full codemod coming in zt tryify
๐ง)
See the main repository for contribution guidelines.
MIT
FAQs
Core ZeroThrow functionality - Rust-style Result<T,E> for TypeScript
The npm package @zerothrow/core receives a total of 78 weekly downloads. As such, @zerothrow/core popularity was classified as not popular.
We found that @zerothrow/core 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.
Research
/Security News
A flawed sandbox in @nestjs/devtools-integration lets attackers run code on your machine via CSRF, leading to full Remote Code Execution (RCE).
Product
Customize license detection with Socketโs new license overlays: gain control, reduce noise, and handle edge cases with precision.
Product
Socket now supports Rust and Cargo, offering package search for all users and experimental SBOM generation for enterprise projects.