Skip to content

Instantly share code, notes, and snippets.

@jmakeig
Last active November 26, 2025 19:04
Show Gist options
  • Select an option

  • Save jmakeig/fd94814909aba4dee0a56ab988caa60e to your computer and use it in GitHub Desktop.

Select an option

Save jmakeig/fd94814909aba4dee0a56ab988caa60e to your computer and use it in GitHub Desktop.

Creates a resuable rules that can validate multiple inputs, returning a valid and cleaned-up value or an accumlation of errors.

Playground

type Maybe<T> = T | unknown;
type Invalid<V = unknown> = { input: V; issues: string[]; }
type MaybeInvalid<T> = Maybe<T> | Invalid;
function is_invalid<V>(result: unknown): result is Invalid<V> {
return 'object' === typeof result
&& null !== result
&& 'input' in result
&& 'issues' in result
&& Array.isArray(result.issues);
}
class Schema<T> {
#steps: Array<(v: unknown) => MaybeInvalid<T>> = [];
constructor() { }
exists(message?: string): this {
this.#steps.push((value) => Schema.exists(value, message));
return this;
}
string(trim = false, message?: string): this {
this.#steps.push((value) => Schema.string(value, trim, message));
return this;
}
length(min: number, max?: number, message?: string): this {
this.#steps.push((value) => Schema.len(value, min, message));
return this;
}
validate<T>(value: unknown): MaybeInvalid<T> {
const issues = [];
let result = value, i = 0;
for (const step of this.#steps) {
result = step(result);
if (is_invalid(result)) {
issues.push(...result.issues);
result = result.input;
}
console.log(`Step ${i++}`, result);
}
if (0 === issues.length) return result;
return {
input: value,
issues
}
}
// Static
static exists<T>(value: unknown, message: string = 'Doesn’t exist'): MaybeInvalid<T> {
if (undefined === value || null === value) {
return {
input: value,
issues: [message]
}
}
return value;
}
static string(value: unknown, trim = false, message: string = `Needs to be a string`): MaybeInvalid<string> {
// Precondition
if (!Schema.exists(value)) {
return {
input: value,
issues: []
}
}
if ('string' === typeof value) {
return trim ? value.trim() : value;
}
return {
input: value,
issues: [message]
}
}
static len(value: unknown, min: number = 0, message: string = `Not at least ${min} long`): MaybeInvalid<string> {
// Precondition
if (is_invalid(Schema.exists(value)) || is_invalid(Schema.string(value))) {
// console.error('len precondition failed');
return {
input: value,
issues: []
}
}
if ('string' === typeof value && value.length >= min) {
return value;
}
return {
input: value,
issues: [message]
};
}
}
const name_schema = new Schema()
.exists()
.string(true)
.length(10);
// console.log(name_schema.validate('This is a lot of text that should be valid.'));
console.log(name_schema.validate(44));
// console.log(name_schema.validate('4 '));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment