@typesugar/validate
v0.1.1
Published
🧊 Zero-cost validation and schema macros for typesugar
Downloads
38
Maintainers
Readme
@typesugar/validate
Zero-cost validation macros and library-agnostic Schema typeclass for typesugar.
Compile-time type guards, assertions, and validation with rich error accumulation.
Installation
npm install @typesugar/validateRequires the typesugar transformer to be configured in your build.
Usage
Type Guards
Generate type guards at compile time:
import { is } from "@typesugar/validate";
interface User {
name: string;
age: number;
email?: string;
}
const isUser = is<User>();
if (isUser(data)) {
// data is typed as User
console.log(data.name);
}Assertions
Assert and narrow types with runtime checks:
import { assert } from "@typesugar/validate";
const assertUser = assert<User>();
const user = assertUser(data); // throws if invalid
console.log(user.name); // user is typed as UserValidation with Error Accumulation
Collect all validation errors instead of failing fast:
import { validate } from "@typesugar/validate";
const validateUser = validate<User>();
const result = validateUser(data);
// result: ValidatedNel<ValidationError, User>
result.fold(
(errors) => console.error("Validation failed:", errors),
(user) => console.log("Valid user:", user.name)
);Schema Typeclass
The Schema<F> typeclass abstracts over validation libraries, enabling library-agnostic validation code with zero-cost specialization.
import { Schema, NativeSchema, nativeSchema, Validator } from "@typesugar/validate";What is Schema?
Schema<F> is a typeclass where F represents a type-level function for schema types:
- For Zod:
Kind<ZodF, User>=ZodType<User> - For Valibot:
Kind<ValibotF, User>=BaseSchema<User> - For native validators:
Kind<ValidatorF, User>=(x: unknown) => x is User
Schema Interface
interface Schema<F> {
/** Parse data and return the validated value, or throw on failure */
readonly parse: <A>(schema: Kind<F, A>, data: unknown) => A;
/** Parse data and return a ValidatedNel with accumulated errors */
readonly safeParse: <A>(schema: Kind<F, A>, data: unknown) => ValidatedNel<ValidationError, A>;
}Native Schema Instance
For validators generated by is<T>():
import { nativeSchema, Validator } from "@typesugar/validate";
// Use with native type guard validators
const isUser = is<User>();
// Parse or throw
const user = nativeSchema.parse(isUser, data);
// Parse with error accumulation
const result = nativeSchema.safeParse(isUser, data);Library-Agnostic Validation
Write validation code that works with any library:
import { Schema } from "@typesugar/validate";
import { specialize } from "@typesugar/specialize";
// Generic validation function
function processBody<F, A>(S: Schema<F>, schema: Kind<F, A>, data: unknown): A {
return S.parse(schema, data);
}
// With specialize(), this becomes zero-cost:
// const processBodyZod = specialize(processBody, zodSchema);
// Compiles to: (schema, data) => schema.parse(data)Derived Operations
The following operations are derived from the base Schema interface:
import {
parseOrElse,
parseMap,
parseChain,
parseAll,
safeParseAll,
nativeParseOrElse,
nativeParseMap,
nativeParseAll,
nativeSafeParseAll,
} from "@typesugar/validate";
// Parse with fallback
const user = parseOrElse(S)(schema, data, defaultUser);
// Parse and transform
const name = parseMap(S)(schema, data, (user) => user.name);
// Chain parsers
const result = parseChain(S)(rawSchema, userSchema, data);
// Validate multiple values
const users = parseAll(S)(schema, dataArray);
// Validate multiple with error accumulation
const results = safeParseAll(S)(schema, dataArray);Creating Custom Schema Instances
import { makeSchema, makeNativeSchema, ValidationError } from "@typesugar/validate";
import { ValidatedNel, Valid, invalidNel } from "@typesugar/fp";
// For HKT-style schemas
const mySchema = makeSchema<MySchemaF>(
(schema, data) => schema.parse(data),
(schema, data) => {
try {
return Valid(schema.parse(data));
} catch (e) {
return invalidNel({ path: "$", message: String(e) });
}
}
);
// For native validators
const myNativeSchema = makeNativeSchema(
(validator, data) => {
if (validator(data)) return data;
throw new Error("Validation failed");
},
(validator, data) => {
if (validator(data)) return Valid(data);
return invalidNel({ path: "$", message: "Validation failed" });
}
);Zero-Cost
All validation logic is generated at compile time. The generated code is the same as if you wrote the checks by hand:
// Generated code for is<User>()
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
typeof (value as any).name === "string" &&
typeof (value as any).age === "number" &&
((value as any).email === undefined || typeof (value as any).email === "string")
);
}When using the Schema typeclass with specialize(), the dictionary is eliminated:
// Before specialization
const user = S.parse(schema, data);
// After specialization (zero-cost)
const user = schema.parse(data);API Reference
Macros
| Macro | Description |
| --------------- | ----------------------------------------------------------------- |
| is<T>() | Generate a type guard (x: unknown) => x is T |
| assert<T>() | Generate an assertion (x: unknown) => T that throws on failure |
| validate<T>() | Generate a validator returning ValidatedNel<ValidationError, T> |
Types
| Type | Description |
| ----------------- | -------------------------------------------- |
| Schema<F> | Typeclass for validation libraries |
| NativeSchema | Specialized Schema for native validators |
| Validator<A> | Type guard function (x: unknown) => x is A |
| ValidationError | Error with path and message fields |
Functions
| Function | Description |
| ------------------------------------ | ----------------------------------------------- |
| nativeSchema | Pre-built Schema instance for native validators |
| makeSchema<F>(parse, safeParse) | Create a Schema instance |
| makeNativeSchema(parse, safeParse) | Create a NativeSchema instance |
| parseOrElse(S) | Parse with fallback value |
| parseMap(S) | Parse and transform result |
| parseChain(S) | Chain two parsers |
| parseAll(S) | Parse array of values |
| safeParseAll(S) | Parse array with error accumulation |
Current Limitations
- Array validation is shallow:
validate<T>()checksArray.isArrayfor array-typed fields but does not validate individual element types. - Literal type unions not yet validated: Union types like
"a" | "b"are not checked against their literal members at runtime. - Nested objects checked structurally but not deeply: Nested object fields are confirmed to be objects, but their inner properties are not recursively validated.
License
MIT
