Generic Schemas
▶ Open in PlaygroundUse generic(fn) to create reusable, parameterized schema templates. The template function accepts one or more schema builders as arguments and returns a concrete schema. Call .apply(...schemas)to instantiate the template with specific schemas — TypeScript infers the resulting type automatically from the function's own generic signature.
Single type parameter — a paginated list that works for any element type:
import {
generic, object, array, number, string,
type SchemaBuilder, type InferType
} from '@cleverbrush/schema';
const PaginatedList = generic(
<T extends SchemaBuilder<any, any, any, any, any>>(itemSchema: T) =>
object({
items: array(itemSchema),
total: number(),
page: number(),
})
);
// Instantiate with a concrete schema — TypeScript infers the full type
const PaginatedUsers = PaginatedList.apply(
object({ name: string(), age: number() })
);
type PaginatedUsersType = InferType<typeof PaginatedUsers>;
// → { items: { name: string; age: number }[]; total: number; page: number }
PaginatedUsers.validate({
items: [{ name: 'Alice', age: 30 }],
total: 1,
page: 1
});
// { valid: true, object: { items: [...], total: 1, page: 1 } }Multiple type parameters — a Result / Either type with independent value and error schemas:
import {
generic, object, boolean, string, number,
type SchemaBuilder, type InferType
} from '@cleverbrush/schema';
const Result = generic(
<
T extends SchemaBuilder<any, any, any, any, any>,
E extends SchemaBuilder<any, any, any, any, any>
>(
valueSchema: T,
errorSchema: E
) =>
object({
ok: boolean(),
value: valueSchema.optional(),
error: errorSchema.optional(),
})
);
const StringResult = Result.apply(string(), number());
type StringResultType = InferType<typeof StringResult>;
// → { ok: boolean; value?: string; error?: number }
StringResult.validate({ ok: true, value: 'hello' }); // valid
StringResult.validate({ ok: false, error: 404 }); // validDefault arguments — supply a defaults array as the first argument so the template can be validated directly without calling .apply() first:
import {
generic, object, array, number, any,
type SchemaBuilder
} from '@cleverbrush/schema';
// 'any()' is the default for the single type parameter
const AnyList = generic(
[any()],
<T extends SchemaBuilder<any, any, any, any, any>>(itemSchema: T) =>
object({ items: array(itemSchema), total: number() })
);
// Validate directly — uses the 'any()' default
AnyList.validate({ items: [1, 'two', true], total: 3 }); // valid
// Or apply concrete schemas first for a stricter type
AnyList.apply(string()).validate({ items: ['a', 'b'], total: 2 }); // validTip: Each call to.apply()returns an independent schema builder. You can call.optional(),.addValidator(),.default(value), and every other fluent method on the result just like you would on any other schema.