A simple, powerful Result type for JavaScript with API serialization support
npm install @jucie.io/resultA simple, powerful Result type for JavaScript with built-in API serialization support.
- ๐ Zero Dependencies - Lightweight and fast
- ๐งน Simple - No symbols, no hidden properties, just plain objects
- ๐ฏ Three States - Success/Failure/Error (not just Ok/Err)
- ๐ Chainable - Fluent API with .onSuccess() / .onFailure() / .onError()
- ๐ API-Ready - Built-in serialization with Result.fetch() and Result.parse()
- โก Async Support - fromPromise(), all(), and more
- โ๏ธ Flexible Naming - Use success() or createSuccess(), your choice
- ๐ TypeScript - Full type definitions included
``bash`
npm install @brickworks/result
`javascript
import { Result } from '@brickworks/result';
// Create results
const ok = Result.success({ id: 123, name: 'Alice' });
const fail = Result.failure('User not found');
// Chain handlers
ok
.onSuccess(r => console.log('User:', r.value))
.onFailure(r => console.error('Failed:', r.reason));
// Transform
const greeting = Result.map(ok, user => Hello ${user.name}!);
// Pattern match
Result.match(ok, {
success: r => console.log(r.value),
failure: r => console.error(r.reason),
error: e => console.error(e.message)
});
`
`javascript
import express from 'express';
import { Result } from '@brickworks/result';
const app = express();
Result.setThrowErrors(false);
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (user) {
res.json(Result.success(user));
} else {
res.status(404).json(Result.failure('User not found'));
}
});
`
Option 1: Using Result.fetch() (simplest)
`javascript
import { Result } from '@brickworks/result';
// One line - fetch and parse automatically
const result = await Result.fetch('/api/users/123');
result
.onSuccess(r => displayUser(r.value))
.onFailure(r => showError(r.reason))
.onError(e => logError(e.message));
`
Option 2: Using fetchResult() (named import)
`javascript
import { fetchResult } from '@brickworks/result';
const result = await fetchResult('/api/users/123');
`
Option 3: Manual fetch with Result.parse()
`javascript`
const response = await fetch('/api/users/123');
const result = Result.parse(await response.text());
Why it works: Results serialize to JSON with a _result: true marker, and Result.fetch()/Result.parse() automatically restore all methods.
| State | Use Case | Example |
|-------|----------|---------|
| Success | Operation succeeded | User found, data saved |
| Failure | Expected business failure | Validation failed, not found |
| Error | Unexpected exception | DB crash, network timeout |
`javascript
import { Result } from '@brickworks/result';
// Success - when things work
const ok = Result.success({ userId: 123 });
// Failure - expected problems (validation, not found, etc.)
const fail = Result.failure('Invalid email', { field: 'email' }, 'VALIDATION_ERROR');
// Error - unexpected problems (system errors, crashes)
Result.setThrowErrors(false); // Don't throw, return error object
const err = Result.error('Database connection failed', { host: 'localhost' });
`
`javascript
// All three methods support: (value/reason/message, data?, code?)
Result.success(data)
Result.failure(reason)
Result.error(message) // Set Result.setThrowErrors(false) first
// Also available with explicit names:
Result.createSuccess(data)
Result.createFailure(reason)
Result.createError(message)
`
`javascript
// Fetch and auto-parse Result from API
const result = await Result.fetch(url, options);
// Parse JSON string to Result
const result = Result.parse(jsonString);
// Traditional approach (still works)
const result = JSON.parse(jsonString, Result.revive);
`
`javascript
Result.isResult(value) // โ true if any Result type
Result.isSuccess(result) // โ true if success
Result.isFailure(result) // โ true if failure
Result.isError(result) // โ true if error
// Aliases
Result.successful(result)
Result.failed(result)
Result.errored(result)
`
`javascript`
Result.getValue(result) // โ value (throws if not success)
Result.getReason(result) // โ reason (throws if not failure)
Result.getData(result) // โ data object (always safe)
`javascript
result
.onSuccess(r => console.log('Value:', r.value))
.onFailure(r => console.error('Reason:', r.reason))
.onError(e => console.error('Error:', e.message));
// Returns the same result for chaining
`
`javascript
// map - transform success value
const doubled = Result.map(result, val => val * 2);
// mapFailure - transform failure reason
const mapped = Result.mapFailure(result, reason => reason.toUpperCase());
// flatMap - chain operations that return Results
const chained = Result.flatMap(result, val => getUserById(val));
// match - pattern matching (returns value)
const output = Result.match(result, {
success: r => r.value,
failure: r => 'default',
error: e => null
});
// unwrapOr - extract with fallback
const value = Result.unwrapOr(result, 'default');
// unwrapOrElse - extract with computed fallback
const value = Result.unwrapOrElse(result, r => computeDefault(r));
`
`javascript
// Convert Promise to Result
const result = await Result.fromPromise(
fetch('/api/users'),
{
failureCode: 'FETCH_FAILED',
mapError: e => ({ reason: e.message, data: {} })
}
);
// Combine multiple Results (short-circuits on first failure)
const results = await Result.all([
Result.fetch('/api/users/1'),
Result.fetch('/api/users/2'),
Result.fetch('/api/users/3')
]);
`
`javascript
// Serialize (happens automatically with res.json())
const json = JSON.stringify(result);
// โ {"kind":"success","ok":true,"value":...,"_result":true}
// Deserialize
const result = Result.parse(json);
// or: JSON.parse(json, Result.revive)
// Manual conversion
const wire = Result.toWire(result);
const result = Result.fromWire(wire);
`
`javascript
import { Result } from '@brickworks/result';
export const api = {
getUser: (id) => Result.fetch(/api/users/${id}),/api/users/${id}
createUser: (data) => Result.fetch('/api/users', {
method: 'POST',
body: JSON.stringify(data)
}),
updateUser: (id, data) => Result.fetch(, {
method: 'PUT',
body: JSON.stringify(data)
})
};
// Usage
const userResult = await api.getUser(123);
userResult.onSuccess(r => displayUser(r.value));
`
`javascript
function validateEmail(email) {
return email.includes('@')
? Result.success(email)
: Result.failure('Invalid email', { field: 'email' });
}
function validatePassword(password) {
return password.length >= 8
? Result.success(password)
: Result.failure('Password too short', { field: 'password' });
}
function validateForm(data) {
const emailResult = validateEmail(data.email);
const passwordResult = validatePassword(data.password);
return Result.flatMap(emailResult, email =>
Result.map(passwordResult, password =>
({ email, password })
)
);
}
`
`javascript
import { useState, useEffect } from 'react';
import { Result } from '@brickworks/result';
function useApi(url) {
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
Result.fetch(url).then(r => {
setResult(r);
setLoading(false);
});
}, [url]);
return { result, loading };
}
// Use it
function UserProfile({ userId }) {
const { result, loading } = useApi(/api/users/${userId});`
if (loading) return Loading...;
return Result.match(result, {
success: r => Welcome {r.value.name}!,
failure: r => Error: {r.reason},
error: e => System error
});
}
`javascript
const result = await api.login(email, password);
result
.onSuccess(r => {
localStorage.setItem('token', r.value.token);
navigate('/dashboard');
})
.onFailure(r => {
switch (r.code) {
case 'VALIDATION_ERROR':
highlightField(r.data.field);
break;
case 'AUTH_FAILED':
showError('Invalid credentials');
break;
case 'USER_NOT_FOUND':
showError('User not found');
break;
}
})
.onError(e => {
logToSentry(e);
showGenericError();
});
`
`javascript
// Control error throwing (default: true)
Result.setThrowErrors(false); // Don't throw, return error objects
// Logging
Result.enableLog();
Result.setLogLevel('debug'); // 'error' | 'warn' | 'info' | 'debug'
Result.setMaxLog(1000);
Result.printLog();
Result.clearLog();
`
Full type definitions included:
`typescript
import { Result, SuccessResult, FailureResult, ErrorResult } from '@brickworks/result';
async function getUser(id: number): Promise
return Result.fetch);
}
const result = await getUser(123);
if (Result.isSuccess(result)) {
result.value.name; // TypeScript knows the type!
}
`
The library supports both terse and explicit naming:
`javascript
// Terse (common in FP)
import { success, failure, error } from '@brickworks/result';
// Explicit (self-documenting)
import { createSuccess, createFailure, createError } from '@brickworks/result';
// Both styles work the same!
`
`javascript
// โ Traditional (throw/catch)
try {
const response = await fetch('/api/users/1');
const data = await response.json();
console.log(data);
} catch (err) {
console.error(err); // Lost context!
}
// โ
Result (explicit handling)
const result = await Result.fetch('/api/users/1');
result
.onSuccess(r => console.log(r.value))
.onFailure(r => showError(r.reason, r.code)) // Rich context
.onError(e => logToSentry(e)); // Type-safe
`
| Feature | @brickworks/result | Others |
|---------|---------------|--------|
| Three states | Success/Failure/Error | Usually just Ok/Err |
| API serialization | Built-in | Manual |
| Fetch wrapper | Result.fetch() | โ |Result.parse()
| Parse helper | | โ |.onSuccess()
| Chainable methods | etc. | Varies |
| Rich metadata | codes, timestamps, data | Limited |
| Dependencies | Zero | Varies |
95 tests passing. Run with:
`bash``
npm test
ISC
Contributions welcome! Please feel free to submit a Pull Request.