Real-world patterns using alchemy primitives. Every snippet is valid TypeScript.
- Safe API call
- Form validation
- Safe config loading
- Safe nested access
- Collecting errors from parallel operations
- Async retry pipeline
- Option null-safe navigation
- Concurrent processing with accumulated errors
- Data transformation pipeline
- Config with defaults + validation
Use case: Fetch data from an API, transform it, and handle errors without try/catch.
import {
fromPromise,
flatMapAsync,
mapResultAsync,
match,
} from '@tecnomancy/alchemy';
interface User {
id: string;
name: string;
roleId: string;
}
interface Role {
id: string;
label: string;
}
interface EnrichedUser extends User {
role: Role;
}
const fetchUser = (id: string): Promise<User> =>
fetch(`/api/users/${id}`).then(r => r.json() as Promise<User>);
const fetchRole = (roleId: string): Promise<Role> =>
fetch(`/api/roles/${roleId}`).then(r => r.json() as Promise<Role>);
const enrichUser = async (user: User): Promise<EnrichedUser> => ({
...user,
role: await fetchRole(user.roleId),
});
const getEnrichedUser = async (id: string) => {
const result = await fromPromise(fetchUser(id))
.then(flatMapAsync(user => fromPromise(enrichUser(user))))
.then(r => mapResultAsync(async (u: EnrichedUser) => u)(r));
return match(
(user: EnrichedUser) => ({ status: 'ok' as const, user }),
(error: Error) => ({ status: 'error' as const, message: error.message }),
)(result);
};Use case: Validate multiple fields, collect all errors at once instead of stopping at the first failure.
import { Ok, Err, validateAll, collectErrors, match } from '@tecnomancy/alchemy';
import type { Result } from '@tecnomancy/alchemy';
interface SignupForm {
email: string;
password: string;
age: number;
}
const validateEmail = (form: SignupForm): Result<SignupForm, string> =>
form.email.includes('@') ? Ok(form) : Err('Email is invalid');
const validatePassword = (form: SignupForm): Result<SignupForm, string> =>
form.password.length >= 8
? Ok(form)
: Err('Password must be at least 8 characters');
const validateAge = (form: SignupForm): Result<SignupForm, string> =>
form.age >= 18 ? Ok(form) : Err('Must be 18 or older');
const validateForm = (form: SignupForm) => {
const results = [
validateEmail(form),
validatePassword(form),
validateAge(form),
];
return match(
() => ({ ok: true as const, form }),
(errors: string[]) => ({ ok: false as const, errors }),
)(collectErrors(results));
};
// Usage
const result = validateForm({ email: 'bad', password: 'short', age: 16 });
// { ok: false, errors: ['Email is invalid', 'Password must be at least 8 characters', 'Must be 18 or older'] }Use case: Parse and validate a JSON config file without crashing on bad input.
import { tryCatch, flatMap, unwrapOrElse } from '@tecnomancy/alchemy';
import { Ok, Err } from '@tecnomancy/alchemy';
import type { Result } from '@tecnomancy/alchemy';
interface AppConfig {
apiUrl: string;
timeout: number;
retries: number;
}
const defaultConfig: AppConfig = {
apiUrl: 'http://localhost:3000',
timeout: 5000,
retries: 3,
};
const safeParse = tryCatch(JSON.parse);
const validateConfig = (raw: unknown): Result<AppConfig, string> => {
if (typeof raw !== 'object' || raw === null) return Err('Config is not an object');
const r = raw as Record<string, unknown>;
if (typeof r['apiUrl'] !== 'string') return Err('apiUrl must be a string');
if (typeof r['timeout'] !== 'number') return Err('timeout must be a number');
if (typeof r['retries'] !== 'number') return Err('retries must be a number');
return Ok(raw as AppConfig);
};
const loadConfig = (json: string): AppConfig =>
unwrapOrElse(
(err: unknown) => {
console.warn('Config error:', err, '— using defaults');
return defaultConfig;
},
)(flatMap(validateConfig)(safeParse(json)));
// Usage
loadConfig('{"apiUrl":"https://api.example.com","timeout":3000,"retries":2}');
// AppConfig { apiUrl: 'https://api.example.com', timeout: 3000, retries: 2 }
loadConfig('not json');
// AppConfig (defaults) — logs warningUse case: Read a deeply nested value that may not exist at any level without optional chaining.
import { getPathOr, fromNullable, mapOption, unwrapOptionOr } from '@tecnomancy/alchemy';
import { pipe } from '@tecnomancy/alchemy';
interface Address {
street?: string;
city?: string;
zip?: string;
}
interface Profile {
address?: Address;
bio?: string;
}
interface User {
id: string;
name: string;
profile?: Profile;
}
const users: Record<string, User> = {
alice: {
id: '1',
name: 'Alice',
profile: { address: { city: 'São Paulo', zip: '01310-100' } },
},
};
// Using getPathOr for a safe path read with fallback
const getCity = (userId: string): string =>
getPathOr('Unknown city', ['profile', 'address', 'city'])(
users[userId] ?? {},
);
getCity('alice'); // 'São Paulo'
getCity('bob'); // 'Unknown city'
// Using Option for null-safe navigation with transformations
const formatCity = (userId: string): string =>
pipe(
fromNullable(users[userId]),
mapOption(u => u.profile),
mapOption(p => p?.address),
mapOption(a => a?.city),
unwrapOptionOr('Unknown city'),
);
formatCity('alice'); // 'São Paulo'
formatCity('bob'); // 'Unknown city'Use case: Run multiple async operations concurrently and collect all failures instead of stopping at the first.
import { mapAsyncResult, collectErrors, match } from '@tecnomancy/alchemy';
import { Ok, Err } from '@tecnomancy/alchemy';
import type { Result } from '@tecnomancy/alchemy';
interface OrderItem {
productId: string;
quantity: number;
}
interface StockCheck {
productId: string;
available: boolean;
}
const checkStock = async (item: OrderItem): Promise<Result<StockCheck, string>> => {
const stock = await fetch(`/api/stock/${item.productId}`).then(r => r.json() as Promise<{ available: boolean }>);
return stock.available
? Ok({ productId: item.productId, available: true })
: Err(`Product ${item.productId} is out of stock`);
};
const validateOrder = async (items: OrderItem[]) => {
const results = await mapAsyncResult(checkStock)(items);
return match(
(checks: StockCheck[]) => ({
valid: true as const,
checks,
}),
(errors: string[]) => ({
valid: false as const,
errors,
}),
)(collectErrors(results));
};
// Usage
const order = [
{ productId: 'A1', quantity: 2 },
{ productId: 'B2', quantity: 1 },
{ productId: 'C3', quantity: 5 },
];
const validation = await validateOrder(order);
// { valid: false, errors: ['Product B2 is out of stock', 'Product C3 is out of stock'] }Use case: Fetch from an unstable API with automatic retries and exponential backoff.
import { pipeAsync, retry, match } from '@tecnomancy/alchemy';
import { fromPromise } from '@tecnomancy/alchemy';
interface ApiResponse {
data: unknown;
status: number;
}
const fetchWithTimeout = async (url: string): Promise<ApiResponse> => {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return { data: await res.json(), status: res.status };
} finally {
clearTimeout(timer);
}
};
// Retry up to 3 times, starting with 500ms delay (doubles each attempt: 500, 1000, 2000)
const resilientFetch = retry(3, 500);
const callApi = async (endpoint: string) => {
const result = await resilientFetch(() => fetchWithTimeout(endpoint));
return match(
(response: ApiResponse) => response.data,
(error: Error) => {
console.error(`All retries failed: ${error.message}`);
return null;
},
)(result);
};
// Or compose into a full async pipeline
const processEndpoint = pipeAsync(
(url: string) => retry(3, 500)(() => fetchWithTimeout(url)),
result =>
match(
(r: ApiResponse) => Promise.resolve(r.data),
(e: Error) => Promise.reject(e),
)(result),
);Use case: Chain optional property accesses through a deeply nested object without intermediate null checks.
import { fromNullable, flatMapOption, mapOption, unwrapOptionOr } from '@tecnomancy/alchemy';
import { pipe } from '@tecnomancy/alchemy';
interface Coordinates {
lat: number;
lng: number;
}
interface Location {
name: string;
coordinates?: Coordinates;
}
interface Event {
title: string;
location?: Location;
}
interface User {
name: string;
upcomingEvent?: Event;
}
const formatEventLocation = (user: User): string =>
pipe(
fromNullable(user.upcomingEvent),
flatMapOption(event => fromNullable(event.location)),
flatMapOption(location => fromNullable(location.coordinates)),
mapOption(coords => `${coords.lat.toFixed(4)}, ${coords.lng.toFixed(4)}`),
unwrapOptionOr('Location unavailable'),
);
// With full data
formatEventLocation({
name: 'Alice',
upcomingEvent: {
title: 'Conference',
location: {
name: 'Convention Center',
coordinates: { lat: -23.5505, lng: -46.6333 },
},
},
});
// '-23.5505, -46.6333'
// Missing at any level
formatEventLocation({ name: 'Bob' });
// 'Location unavailable'
formatEventLocation({ name: 'Carol', upcomingEvent: { title: 'Webinar' } });
// 'Location unavailable'Use case: Process a batch of items concurrently (bounded concurrency) and surface all failures.
import { mapConcurrentResult, collectErrors, match } from '@tecnomancy/alchemy';
import { Ok, Err } from '@tecnomancy/alchemy';
import type { Result } from '@tecnomancy/alchemy';
interface Report {
userId: string;
month: string;
}
interface ReportResult {
userId: string;
url: string;
}
const generateReport = async (report: Report): Promise<Result<ReportResult, string>> => {
try {
const url = await fetch('/api/reports', {
method: 'POST',
body: JSON.stringify(report),
}).then(r => r.json() as Promise<{ url: string }>).then(j => j.url);
return Ok({ userId: report.userId, url });
} catch (err) {
return Err(`Failed to generate report for ${report.userId}: ${String(err)}`);
}
};
const generateAllReports = async (reports: Report[]) => {
// Process at most 3 concurrently
const results = await mapConcurrentResult(3, generateReport)(reports);
return match(
(generated: ReportResult[]) => ({
success: true as const,
count: generated.length,
urls: generated.map(r => r.url),
}),
(errors: string[]) => ({
success: false as const,
failedCount: errors.length,
errors,
}),
)(collectErrors(results));
};Use case: Transform raw API data through a series of pure functions using pipe, array utilities, and object utilities.
import { pipe } from '@tecnomancy/alchemy';
import { map, filter, sortBy, groupBy } from '@tecnomancy/alchemy';
import { pick, mapValues } from '@tecnomancy/alchemy';
interface RawProduct {
id: string;
name: string;
price: number;
category: string;
inStock: boolean;
internalCode: string;
}
interface ClientProduct {
id: string;
name: string;
price: number;
category: string;
}
type Catalog = Record<string, ClientProduct[]>;
const buildCatalog = (products: RawProduct[]): Catalog =>
pipe(
products,
filter((p: RawProduct) => p.inStock),
map((p: RawProduct): ClientProduct => pick(['id', 'name', 'price', 'category'])(p)),
sortBy((p: ClientProduct) => p.price),
groupBy((p: ClientProduct) => p.category),
);
// Apply a discount to every price in the catalog
const applyDiscount = (pct: number) => (catalog: Catalog): Catalog =>
mapValues(
(products: ClientProduct[]) =>
products.map(p => ({ ...p, price: +(p.price * (1 - pct / 100)).toFixed(2) })),
)(catalog);
// Full pipeline
const clientCatalog = pipe(
rawProducts,
buildCatalog,
applyDiscount(10), // 10% off
);Use case: Merge user-supplied config over defaults, then validate the result.
import { defaults, validateAll, match } from '@tecnomancy/alchemy';
import { Ok, Err } from '@tecnomancy/alchemy';
import type { Result } from '@tecnomancy/alchemy';
interface ServerConfig {
host: string;
port: number;
maxConnections: number;
timeout: number;
debug: boolean;
}
const baseConfig: ServerConfig = {
host: 'localhost',
port: 3000,
maxConnections: 100,
timeout: 30_000,
debug: false,
};
const isValidPort = (cfg: ServerConfig): Result<ServerConfig, string> =>
cfg.port >= 1 && cfg.port <= 65535
? Ok(cfg)
: Err(`Invalid port: ${cfg.port}`);
const isValidMaxConnections = (cfg: ServerConfig): Result<ServerConfig, string> =>
cfg.maxConnections > 0
? Ok(cfg)
: Err('maxConnections must be positive');
const isValidTimeout = (cfg: ServerConfig): Result<ServerConfig, string> =>
cfg.timeout >= 1000
? Ok(cfg)
: Err('timeout must be at least 1000ms');
const buildConfig = (partial: Partial<ServerConfig>) =>
match(
(cfg: ServerConfig) => cfg,
(error: string) => {
throw new Error(`Invalid config: ${error}`);
},
)(
validateAll([isValidPort, isValidMaxConnections, isValidTimeout])(
defaults(baseConfig)(partial),
),
);
// Usage
buildConfig({ port: 8080 });
// ServerConfig { host: 'localhost', port: 8080, maxConnections: 100, timeout: 30000, debug: false }
buildConfig({ port: 0 });
// throws Error('Invalid config: Invalid port: 0')