Ts Result Zod
Result pattern with Zod instead of Typescript Types a source.
Example Usage
Basic Usage
import z from 'zod';
import {
schemaResult,
schemaResultSuccess,
schemaResultError,
type InferResult,
type InferResultSuccess,
type InferResultError,
} from './ts-result-zod';
const schemaFn1Result = schemaResult(
schemaResultSuccess(z.object({ name: z.string() })),
schemaResultError(['FETCH_FAILED', 'UNKNOWN_ERROR'] as const),
);
type Fn1Result = InferResult<typeof schemaFn1Result>;
const fn1 = async (): Promise<Fn1Result> => {
// ts force you to return a discriminated union
try {
const result = await fetch('https://api.example.com');
// for error not unexpected
if (!result.ok) {
return {
status: 'error',
code: 'FETCH_FAILED',
message: 'Fetch status is not ok: ' + result.status,
};
}
// for success
return {
status: 'success',
data: await result.json(),
};
}
catch (error) {
// for error unexpected
return {
status: 'error',
code: 'UNKNOWN_ERROR',
message: 'Something went wrong with fetch',
};
}
};
Infer Types
import z from 'zod';
import {
schemaResult,
schemaResultSuccess,
schemaResultError,
type InferResult,
type InferResultSuccess,
type InferResultError,
} from './ts-result-zod';
const simpleResult = schemaResult(
schemaResultSuccess(z.object({ name: z.string(), age: z.number() })),
schemaResultError(['MY_ERROR_CODE', 'UNKNOWN_ERROR'] as const),
);
type SimpleResult = InferResult<typeof simpleResult>;
// ⏬
{
status: "success";
data: {
name: string;
age: number;
};
} | {
status: "error";
code: "UNKNOWN_ERROR" | "MY_ERROR_CODE";
message: string;
}
type SimpleResultSuccess = InferResultSuccess<typeof simpleResult>;
// ⏬
{
status: "success";
data: {
name: string;
age: number;
};
}
type SimpleResultError = InferResultError<typeof simpleResult>;
// ⏬
{
status: "error";
code: "UNKNOWN_ERROR" | "MY_ERROR_CODE";
message: string;
}
Wrap Result in an other Result
import z from 'zod';
import {
schemaResult,
schemaResultSuccess,
schemaResultError,
type InferResult,
getResultData,
} from './ts-result-zod';
// define first result
const simpleResult = schemaResult(
schemaResultSuccess(z.object({ name: z.string(), age: z.number() })),
schemaResultError(['MY_ERROR_CODE', 'UNKNOWN_ERROR'] as const),
);
// define second result that wrap the first
const wrappedResult = schemaResult(
schemaResultSuccess(
z.object({
simpleData: getResultData(simpleResult).successData,
extraInfo: z.string(),
}),
),
schemaResultError([
'INVALID_INPUT',
...getResultData(simpleResult).errorCodes
] as const),
);
// infer types of the wrapped result
type WrappedResult = InferResult<typeof wrappedResult>;
// ⏬
{
status: "success";
data: {
simpleData: {
name: string;
age: number;
};
extraInfo: string;
};
} | {
status: "error";
code: "UNKNOWN_ERROR" | "MY_ERROR_CODE" | "INVALID_INPUT";
message: string;
}
// use it
const fnWrapped = async (): Promise<WrappedResult> => {
// ...
}
Dependencies
npmzod
Auto Install
npx shadcn@latest add https://shadcn-registry-ts.vercel.app/r/util-ts-result-zod.json
Manual Install
ts-result-zod.ts
import z from "zod";
type Base = {
SuccessData: z.ZodSchema;
ErrorCodes: readonly [string, ...string[]],
};
/**
* Create a Result Success Zod Schema.
* See {@link schemaResult}
* */
export const schemaResultSuccess = <DataSchema extends Base['SuccessData']>(dataSchema: DataSchema) => z.object({
status: z.literal('success'),
data: dataSchema,
});
/**
* Create a Result Error Zod Schema.
* See {@link schemaResult}
* */
export const schemaResultError = <ErrorCodes extends Base['ErrorCodes']>(errorCodes: ErrorCodes) => z.object({
status: z.literal('error'),
code: z.enum(errorCodes),
message: z.string(),
});
/** Create a Result Zod Schema
* @example
* const result = schemaResult(
* schemaResultSuccess(z.object({ name: z.string() })),
* schemaResultError(['FETCH_FAILED'] as const)
* );
*
* type Result = InferResult<typeof result>;
* // ⏬
* type Result = {
* status: 'success';
* data: {
* name: string
* }
* } | {
* status: 'error';
* code: 'FETCH_FAILED';
* message: string
* }
*
* const fn = async (): Promise<Result> => {
* // ...
* }
* */
export const schemaResult = <
S extends Base['SuccessData'],
E extends Base['ErrorCodes'],
>(
schemaSuccess: ReturnType<typeof schemaResultSuccess<S>>,
schemaError: ReturnType<typeof schemaResultError<E>>,
) => z.discriminatedUnion('status', [
schemaSuccess,
schemaError,
]);
/** Infer Types of a Result. Contains both `success` and `error`.
* @example
* const result = schemaResult(
* schemaResultSuccess(z.object({ name: z.string() })),
* schemaResultError(['FETCH_FAILED'] as const)
* );
*
* type Result = InferResult<typeof result>;
* // ⏬
* {
* status: 'success';
* data: {
* name: string
* }
* } | {
* status: 'error';
* code: 'FETCH_FAILED';
* message: string
* }
* */
export type InferResult<R extends ReturnType<typeof schemaResult>> = z.infer<R>;
/** Infer Types of a Result Success
* @example
* const result = schemaResult(
* schemaResultSuccess(z.object({ name: z.string() })),
* schemaResultError(['FETCH_FAILED'] as const)
* );
*
* type ResultSuccess = InferResultSuccess<typeof result>;
* // ⏬
* {
* status: 'success';
* data: {
* name: string
* }
* }
* */
export type InferResultSuccess<R extends ReturnType<typeof schemaResult>> = Extract<InferResult<R>, { status: 'success'; }>;
/** Infer Types of a Result Error
* @example
* const result = schemaResult(
* schemaResultSuccess(z.object({ name: z.string() })),
* schemaResultError(['FETCH_FAILED'] as const)
* );
*
* type ResultError = InferResultError<typeof result>;
* // ⏬
* {
* status: 'error';
* code: 'FETCH_FAILED';
* message: string
* }
* */
export type InferResultError<R extends ReturnType<typeof schemaResult>> = Extract<InferResult<R>, { status: 'error'; }>;
const getResultSuccess = <
S extends Base['SuccessData'],
E extends Base['ErrorCodes'],
>(
result: ReturnType<typeof schemaResult<S, E>>
) => result.options[0];
const getResultError = <
S extends Base['SuccessData'],
E extends Base['ErrorCodes'],
>(
result: ReturnType<typeof schemaResult<S, E>>
) => result.options[1];
/**
* Extract Data from a Result Zod Schema
* @example
* const result = schemaResult(
* schemaResultSuccess(z.object({ name: z.string() })),
* schemaResultError(['FETCH_FAILED'] as const)
* );
*
* const resultData = getResultData(result);
* // ⏬
* {
* success: schemaResultSuccess(z.object({ name: z.string() })),
* successData: z.object({ name: z.string() }),
* error: schemaResultError(['FETCH_FAILED'] as const),
* errorCodes: ['FETCH_FAILED'] as const,
* }
*
* const wrappedResult = schemaResult(
* schemaResultSuccess(
* z.object({
* extraData: z.string(),
* otherResultData: getResultData(result).successData,
* })
* ),
* schemaResultError([
* 'NEW_ERROR_CODE',
* ...getResultData(result).errorCodes
* ] as const)
* );
*
*/
export const getResultData = <
S extends Base['SuccessData'],
E extends Base['ErrorCodes'],
>(
result: ReturnType<typeof schemaResult<S, E>>
) => {
return {
success: getResultSuccess(result),
successData: getResultSuccess(result).shape.data,
error: getResultError(result),
errorCodes: getResultError(result).shape.code.options,
};
};
Test
No test
Command Palette
Search for a command to run...