fp-async — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited fp-async (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Stop writing nested try/catch blocks. Stop losing error context. Start building clean async pipelines that handle errors properly.
TaskEither is simply an async operation that tracks success or failure. That's it. No fancy terminology needed.
TaskEither.try/catch flows.// TaskEither<Error, User> means:
// "An async operation that either fails with Error or succeeds with User"// BEFORE: Try/catch hell
async function getUserData(userId: string) {
try {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const user = await response.json()
try {
const posts = await fetch(`/api/users/${userId}/posts`)
if (!posts.ok) {
throw new Error(`HTTP ${posts.status}`)
}
const postsData = await posts.json()
return { user, posts: postsData }
} catch (postsError) {
// Now what? Return partial data? Rethrow? Log?
console.error('Failed to fetch posts:', postsError)
return { user, posts: [] }
}
} catch (error) {
// Lost all context about what failed
console.error('Something failed:', error)
throw error
}
}import * as TE from 'fp-ts/TaskEither'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
// One wrapper function - reuse everywhere
const fetchJson = <T>(url: string): TE.TaskEither<Error, T> =>
TE.tryCatch(
async () => {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return response.json()
},
(error) => error instanceof Error ? error : new Error(String(error))
)
// AFTER: Clean and composable
const getUser = (userId: string) => fetchJson<User>(`/api/users/${userId}`)
const getPosts = (userId: string) => fetchJson<Post[]>(`/api/users/${userId}/posts`)TE.tryCatch takes two things:
TE.tryCatch(
() => somePromise, // The async work
(thrown) => toError(thrown) // Convert failures to your error type
)// Wrap a value as success
const success = TE.right<Error, number>(42)
// Wrap a value as failure
const failure = TE.left<Error, number>(new Error('Nope'))
// From a nullable value (null/undefined becomes error)
const fromNullable = TE.fromNullable(new Error('Value was null'))
const result = fromNullable(maybeUser) // TaskEither<Error, User>
// From a condition
const mustBePositive = TE.fromPredicate(
(n: number) => n > 0,
(n) => new Error(`Expected positive, got ${n}`)
)// BEFORE: Deeply nested, hard to follow
async function processOrder(orderId: string) {
try {
const order = await fetchOrder(orderId)
if (!order) throw new Error('Order not found')
try {
const user = await fetchUser(order.userId)
if (!user) throw new Error('User not found')
try {
const inventory = await checkInventory(order.items)
if (!inventory.available) throw new Error('Out of stock')
try {
const payment = await chargePayment(user, order.total)
if (!payment.success) throw new Error('Payment failed')
try {
const shipment = await createShipment(order, user)
return { order, shipment, payment }
} catch (e) {
// Refund payment? Log? What's the state now?
await refundPayment(payment.id)
throw e
}
} catch (e) {
throw e
}
} catch (e) {
throw e
}
} catch (e) {
throw e
}
} catch (e) {
console.error('Order processing failed', e)
throw e
}
}// AFTER: Flat, readable pipeline
const processOrder = (orderId: string) =>
pipe(
fetchOrder(orderId),
TE.chain(order => fetchUser(order.userId)),
TE.chain(user =>
pipe(
checkInventory(order.items),
TE.chain(inventory => chargePayment(user, order.total))
)
),
TE.chain(payment => createShipment(order, user, payment))
)Use map when your transformation is synchronous and can't fail:
pipe(
fetchUser(userId),
TE.map(user => user.name.toUpperCase()) // Just tran~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.