Skip to content

Instantly share code, notes, and snippets.

@sillvva
Last active September 4, 2025 02:33
Show Gist options
  • Select an option

  • Save sillvva/24e1ccd54c87ffa6dbdf563f7cc0cebb to your computer and use it in GitHub Desktop.

Select an option

Save sillvva/24e1ccd54c87ffa6dbdf563f7cc0cebb to your computer and use it in GitHub Desktop.
StandardSchemaV1 Codec
import type { StandardSchemaV1 } from "@standard-schema/spec";
type Context<TType> = {
parsed: TType;
issues: StandardSchemaV1.Issue[];
};
// Transform functions interface
interface CodecTransforms<TInputIn, TInputOut, TOutputIn, TOutputOut> {
decode: (input: TInputIn, context?: Context<TInputOut>) => TOutputIn; // forward transform (input -> output)
encode: (output: TOutputIn, context?: Context<TOutputOut>) => TInputIn; // reverse transform (output -> input)
}
// Main codec utility function
export function codec<TInputIn, TInputOut, TOutputIn, TOutputOut>(
inputSchema: StandardSchemaV1<TInputIn, TInputOut>,
outputSchema: StandardSchemaV1<TOutputIn, TOutputOut>,
transforms: CodecTransforms<TInputIn, TInputOut, TOutputIn, TOutputOut>
) {
const safeDecode = (value: TInputIn): StandardSchemaV1.Result<TOutputOut> => {
const inputResult = inputSchema["~standard"].validate(value);
if (inputResult instanceof Promise)
throw new Error(`${inputSchema["~standard"].vendor} validator is async. Use decodeAsync or safeDecodeAsync instead.`);
if (inputResult.issues) return inputResult;
const context: Context<TInputOut> = { parsed: inputResult.value, issues: [] };
const transformed = transforms.decode(value, context);
if (context.issues.length > 0) return { issues: context.issues };
return outputSchema["~standard"].validate(transformed) as StandardSchemaV1.Result<TOutputOut>;
};
const safeEncode = (value: TOutputIn): StandardSchemaV1.Result<TInputOut> => {
const outputResult = outputSchema["~standard"].validate(value);
if (outputResult instanceof Promise)
throw new Error(`${outputSchema["~standard"].vendor} validator is async. Use encodeAsync or safeEncodeAsync instead.`);
if (outputResult.issues) return outputResult;
const context: Context<TOutputOut> = { parsed: outputResult.value, issues: [] };
const transformed = transforms.encode(value, context);
if (context.issues.length > 0) return { issues: context.issues };
return inputSchema["~standard"].validate(transformed) as StandardSchemaV1.Result<TInputOut>;
};
const decode = (value: TInputIn) => {
const result = safeDecode(value);
if (result.issues) throw new ParseError(result.issues);
return result.value;
};
const encode = (value: TOutputIn) => {
const result = safeEncode(value);
if (result.issues) throw new ParseError(result.issues);
return result.value;
};
const safeDecodeAsync = async (value: TInputIn): Promise<StandardSchemaV1.Result<TOutputOut>> => {
const inputResult = await inputSchema["~standard"].validate(value);
if (inputResult.issues) return inputResult;
const context: Context<TInputOut> = { parsed: inputResult.value, issues: [] };
const transformed = transforms.decode(value, context);
if (context.issues.length > 0) return { issues: context.issues };
return await outputSchema["~standard"].validate(transformed);
};
const safeEncodeAsync = async (value: TOutputIn): Promise<StandardSchemaV1.Result<TInputOut>> => {
const outputResult = await outputSchema["~standard"].validate(value);
if (outputResult.issues) return outputResult;
const context: Context<TOutputOut> = { parsed: outputResult.value, issues: [] };
const transformed = transforms.encode(value, context);
if (context.issues.length > 0) return { issues: context.issues };
return await inputSchema["~standard"].validate(transformed);
};
const decodeAsync = async (value: TInputIn) => {
const result = await safeDecodeAsync(value);
if (result.issues) throw new ParseError(result.issues);
return result.value;
};
const encodeAsync = async (value: TOutputIn) => {
const result = await safeEncodeAsync(value);
if (result.issues) throw new ParseError(result.issues);
return result.value;
};
return {
safeDecode,
safeEncode,
decode,
encode,
safeDecodeAsync,
safeEncodeAsync,
decodeAsync,
encodeAsync
};
}
export class ParseError extends Error {
override name = "ParseError";
constructor(public issues: readonly StandardSchemaV1.Issue[]) {
let numIssues = 0;
const message = issues.flatMap((issue) => {
const lines = [`- ${issue.message}`];
if (issue.path) {
lines.push(
` Path: ${issue.path?.reduce<string>((acc, p, idx) => {
const key = typeof p === "object" ? p.key : p;
if (typeof key === "number") return `${acc}[${key}]`;
return idx === 0 ? `${String(key)}` : `${acc}.${String(key)}`;
}, "")}`
);
}
numIssues++;
return lines;
});
message.unshift(`${numIssues} issue${numIssues === 1 ? "" : "s"} detected:`);
super(message.join("\n"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment