skills/fp-refactor/SKILL.md
Comprehensive guide for refactoring imperative TypeScript code to fp-ts functional patterns
npx skillsauth add ranbot-ai/awesome-skills fp-refactorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides comprehensive patterns and strategies for migrating existing imperative TypeScript code to fp-ts functional programming patterns.
try/catch, null checks, callbacks, DI, or loops into functional equivalents.Traditional try-catch blocks have several issues:
function parseJSON(input: string): unknown {
try {
return JSON.parse(input);
} catch (error) {
throw new Error(`Invalid JSON: ${error}`);
}
}
function validateUser(data: unknown): User {
try {
if (!data || typeof data !== 'object') {
throw new Error('Data must be an object');
}
const obj = data as Record<string, unknown>;
if (typeof obj.name !== 'string') {
throw new Error('Name is required');
}
if (typeof obj.age !== 'number') {
throw new Error('Age must be a number');
}
return { name: obj.name, age: obj.age };
} catch (error) {
throw error;
}
}
// Usage with nested try-catch
function processUserInput(input: string): User | null {
try {
const data = parseJSON(input);
const user = validateUser(data);
return user;
} catch (error) {
console.error('Failed to process user:', error);
return null;
}
}
import * as E from 'fp-ts/Either';
import * as J from 'fp-ts/Json';
import { pipe } from 'fp-ts/function';
interface User {
name: string;
age: number;
}
// Use Json.parse which returns Either<Error, Json>
const parseJSON = (input: string): E.Either<Error, unknown> =>
pipe(
J.parse(input),
E.mapLeft((e) => new Error(`Invalid JSON: ${e}`))
);
// Validation returns Either, making errors explicit in types
const validateUser = (data: unknown): E.Either<Error, User> => {
if (!data || typeof data !== 'object') {
return E.left(new Error('Data must be an object'));
}
const obj = data as Record<string, unknown>;
if (typeof obj.name !== 'string') {
return E.left(new Error('Name is required'));
}
if (typeof obj.age !== 'number') {
return E.left(new Error('Age must be a number'));
}
return E.right({ name: obj.name, age: obj.age });
};
// Compose with pipe and flatMap - errors propagate automatically
const processUserInput = (input: string): E.Either<Error, User> =>
pipe(
parseJSON(input),
E.flatMap(validateUser)
);
// Handle both cases explicitly
pipe(
processUserInput('{"name": "Alice", "age": 30}'),
E.match(
(error) => console.error('Failed to process user:', error.message),
(user) => console.log('User:', user)
)
);
T to Either<E, T> where E is your error typethrow new Error(...) to E.left(new Error(...))return value to E.right(value)pipe with E.flatMap to chain operationsasync function fetchUser(id: string): Promise<User> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return validateUser(data);
} catch (error) {
throw new Error(`Failed to fetch user: ${error}`);
}
}
async function fetchUserPosts(userId: string): Promise<Post[]> {
try {
const response = await fetch(`/api/users/${userId}/posts`);
if (!response.ok) {
tools
Use when a user asks to mine or update a private, evidence-backed work profile from local Claude Code, Codex, Copilot CLI, or OpenCode sessions.
data-ai
Use when diagnosing Android overheating, idle heat, thermal throttling, charging or radio heat, or abnormal battery drain with read-only ADB evidence and approval gates.
research
Research public competitor ads, analyze creative patterns and landing pages, and produce an evidence-labeled strategic teardown.
tools
Compiled CLI covering all 52 endpoints of the Anytype local API — objects, properties, tags, search, chat, files — one binary, no MCP server needed.