-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Description
Feels a bit weird to have to return a Result from the callback. My more expected use case would be pre-wrapping functions (possibly on export?) so that I don't need to wrap them in fromUnsafe everywhere I consume.
I would also like the ability to explicitly type Data and Err when creating a safeFn; with the current signature I have to input args as well which IMO is best to always be inferred.
const createErrorResult = (e: unknown, errorHandler?: (e: unknown) => any) =>
({
success: false,
error: errorHandler?.(e) ?? null,
}) as const
const createSuccessResult = <T>(data: T) =>
({
success: true,
data,
}) as const
/**
* Create a safe function from an unsafe one.
*
* Supports two usage patterns:
* 1. Full inference: `safeFn(callback, errorHandler)` - infers Data, Args, and Err from callback
* 2. Explicit types: `safeFn<Data, Err>()(callback, errorHandler)` - specify Data/Err, infer Args
*
* @example
* // Pattern 1: Full inference
* const getName = n.safeFn((first: string, last: string) => {
* return { name: `${first} ${last}` };
* });
* // getName: (first: string, last: string) => Result<{ name: string }, null>
*
* // Pattern 2: Explicit Data/Err types
* const getUser = n.safeFn<User, string>()((id: string) => {
* return db.findUser(id);
* }, () => "FAILED_TO_GET_USER");
* // getUser: (id: string) => Result<User, string>
*/
// Overload 1: Direct call with callback - full inference
function safeFn<Args extends unknown[], Data, Err = null>(
cb: (...args: Args) => Data,
eh?: (e: unknown) => Err,
): (...args: Args) => SafeReturnType<Data, Err>
// Overload 2: Curried call with explicit types - no arguments returns a builder
function safeFn<Data, Err = null>(): <Args extends unknown[]>(
cb: (...args: Args) => Data,
eh?: (e: unknown) => Err,
) => (...args: Args) => SafeReturnType<Data, Err>
// Implementation
function safeFn(
cb?: (...args: any[]) => any,
eh?: (e: unknown) => any,
): any {
const wrapCallback = (
callback: (...args: any[]) => any,
errorHandler?: (e: unknown) => any,
) => {
return (...args: any[]) => {
try {
const result = callback(...args)
if (result instanceof Promise)
return result
.then(createSuccessResult)
.catch((e: unknown) => createErrorResult(e, errorHandler))
return createSuccessResult(result)
} catch (e) {
return createErrorResult(e, errorHandler)
}
}
}
// nonNull assertion required because of the weird functional overload
return wrapCallback(cb!, eh)
}
// Pattern 1: Explicit types with curried call
const _testFn = safeFn<{ name: string }, string>()(
(first: string, last: string) => {
return {
name: `${first} ${last}`,
}
},
) satisfies (first: string, last: string) => Result<{ name: string }, string>
// Pattern 2: Full inference with direct call
const _testFn2 = safeFn((first: string, last: string) => {
return {
name: `${first} ${last}`,
}
}) satisfies (first: string, last: string) => Result<{ name: string }, null>Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels