Funkcia Adoption
Migrate existing error handling and validation into explicit, typed, chainable flows using Funkcia.
Workflow
- •Start at boundaries, not internals.
- •Migrate I/O edges first: API handlers, DB repositories, queue consumers, file readers.
- •Keep module internals stable until boundary contracts return
Option/Result.
- •Classify failure shape before coding.
- •Use
Option/OptionAsyncfor expected absence without an error payload. - •Use
Result/ResultAsyncfor expected failure with explicit error semantics.
- •Use
- •Define domain errors with
TaggedErrorbefore refactoring call chains.- •Model application failures (auth, validation, not-found, rate limit, external dependency).
- •Preserve causes when wrapping infrastructure failures.
- •Transform imperative control flow.
- •Replace
try/catchwithResult.tryorResultAsync.try. - •Replace null/falsy branching with
fromNullable/fromFalsy. - •Prefer generator style for multi-step flows; use
map,andThen,filter,or,matchfor focused one-step transforms.
- •Replace
- •Resolve once at the boundary.
- •Use
matchin handlers/controllers to map outcomes to transport responses. - •For tagged error unions (
_tag), preferexhaustive(error, { ... })to enforce full case handling. - •Avoid
unwrapin business logic.
- •Use
- •Verify behavior and type contracts.
- •Add runtime +
expectTypeOftests. - •Run repository checks before completion.
- •Add runtime +
Migration Strategy
- •Migrate incrementally by vertical slices (feature or module), not by type across the whole codebase.
- •Keep adapters during migration when legacy callers still expect nullable or throwing APIs.
- •Preserve behavior first, then improve ergonomics.
- •Recommend wrapping shared dependencies (database clients, API SDKs, queues) with
ResultAsync.resourceand mapping each dependency to pre-defined resource error types before wiring business flows.
Pattern Transformations
Throwing parse to Result
ts
import { Result } from 'funkcia';
function parseWebhook(raw: string) {
return Result.try(
() => JSON.parse(raw),
() => new Error('Invalid webhook payload'),
);
}
Nullable lookup to Option
ts
import { Option } from 'funkcia';
function findPrimaryEmail(user: User) {
return Option.fromNullable(user.emails.find((x) => x.primary))
.map((email) => email.value.toLowerCase());
}
Rejecting async call to ResultAsync
ts
import { ResultAsync } from 'funkcia';
function fetchCheckoutSession(sessionId: string) {
return ResultAsync.try(
() => payments.getSession(sessionId),
(cause) => new Error(`Failed to fetch session ${sessionId}: ${String(cause)}`),
);
}
Callback Hell vs Generators
Callback hell / nested async handling
ts
async function buildCheckoutSummary(userId: string): Promise<Result<CheckoutSummary, CheckoutError>> {
try {
const user = await usersApi.getById(userId);
if (!user.defaultPaymentMethodId) {
return Result.error(new MissingPaymentMethodError(user.id));
}
const paymentMethod = await paymentsApi.getMethod(user.defaultPaymentMethodId);
const cart = await cartApi.getActiveCart(user.id);
if (!cart) {
return Result.error(new CartNotFoundError(user.id));
}
return Result.ok({
userEmail: user.email,
paymentMethod: paymentMethod.brand,
total: cart.total,
});
} catch (cause) {
return Result.error(new CheckoutInfrastructureError(cause));
}
}
Preferred generator style
ts
function buildCheckoutSummary(userId: string): ResultAsync<CheckoutSummary, CheckoutError> {
return ResultAsync.use(async function* () {
const user = yield* ResultAsync.try(
() => usersApi.getById(userId),
(cause) => new CheckoutInfrastructureError(cause),
);
const paymentMethodId = yield* Result.fromNullable(
user.defaultPaymentMethodId,
() => new MissingPaymentMethodError(user.id),
);
const paymentMethod = yield* ResultAsync.try(
() => paymentsApi.getMethod(paymentMethodId),
(cause) => new CheckoutInfrastructureError(cause),
);
const cart = yield* Result.fromNullable(
yield* ResultAsync.try(
() => cartApi.getActiveCart(user.id),
(cause) => new CheckoutInfrastructureError(cause),
),
() => new CartNotFoundError(user.id),
);
return ResultAsync.ok({
userEmail: user.email,
paymentMethod: paymentMethod.brand,
total: cart.total,
});
});
}
Reference Map
- •
references/tagged-errors.md: Model real application failures withTaggedError. - •
references/exhaustive.md: Useexhaustiveandcorruptfor type-safe branching. - •
references/brand.md: Build branded domain primitives and safe parsers. - •
references/exceptions.md: Use built-in exception types andpanic. - •
references/safe-functions.md: Safely parse JSON and normalize URLs/URIs. - •
references/generators.md: Preferred generator-based style forOption/Resultand async variants. - •
references/do-notation.md: Context-accumulation style withDo,bind,let, andbindTo. - •
references/resources.md: Wrap shared resources withResultAsync.resourceso operations return expected values or pre-defined resource errors.