examiner — The Laravel Pattern in JavaScript
Victor’s first serious project was Van — a Laravel chatbot, April 2014. Two years later, in April 2016, he wrote a JavaScript validation library whose rule syntax is unmistakably Laravel’s:
rules: {
name: 'required|string|min:3',
email: 'required|email',
age: 'required|number|min:18'
}
Pipe-delimited filter names. Colon-separated arguments. Human-readable field name “replaces” for error messages. If you’ve written Laravel validation, you’ve seen this before. The predecessor library, supervalidation (July 2015), was explicit about it — its description read “Based on Laravel 3.x validation module.” Examiner dropped the attribution but kept the architecture.
What it does
Examiner validates JavaScript objects against declarative rule strings. You define the shape your data should have, and it tells you where reality diverges from the spec. Fifty-three commits. Eleven npm versions. One runtime dependency: lodash.
The core is a Validator class that extends Node’s EventEmitter. You give it rules and data. It returns a Promise that resolves if everything passes and rejects with structured error messages if anything fails. After validation, the instance carries state: valid, invalid, dirty, empty. It emits 'update' events. It tracks per-field changes — current value, old value, whether the field has been touched.
If you’ve built forms in AngularJS, those properties should be familiar. Angular’s form controller had $valid, $invalid, $dirty, $pristine. Examiner reproduces the same state model outside of Angular’s directive system. The form validation layer, extracted from the framework, just like parse.js extracted the expression parser and restcase extracted Backbone’s data layer.
The $ wildcard
The most architecturally interesting feature is the $ notation for nested validation:
rules: {
'users.$.documents.$.name': 'required|string'
}
The $ is a wildcard that expands at validation time against the actual data. If users is an array of three objects, each with a documents array of two items, this rule becomes six concrete paths: users.0.documents.0.name through users.2.documents.1.name.
The implementation is recursive. resolveComplexKeys finds the first $ in a rule key, looks up the corresponding value in the data, iterates its entries to generate concrete paths, and calls itself again if any $ markers remain. For 'deep.$.$.b.$.c.$.a', it recurses four times.
This is a real design problem that Victor actually solved. Most JavaScript validation libraries in 2016 handled flat objects or required you to write explicit loops for array validation. The $ wildcard lets you declare the shape of arbitrarily nested data in a single rule string. It’s the kind of feature that only matters when you’re validating real API payloads — not the toy examples in READMEs, but the nested, array-heavy, inconsistently-structured data that actual applications receive.
The framework Victor was building
Examiner was created in April 2016. Here’s what came before it:
- parse.js (July 2015) — expression parser
- ngcomponent (July 2015) — component system
- renderer (November 2015) — DOM compiler
- injector (December 2015) — dependency injection
- location (December 2015) — routing
- moduleloader (December 2015) — module system
- vdom-raw (February 2016) — virtual-dom compiler
Parser. Component system. DOM renderer. Dependency injection. Routing. Module loader. Virtual DOM. And now form validation.
Victor wasn’t just building libraries. He was building a framework. Not officially — there’s no “VictorJS” repo that ties these together, no manifest that says “this is a web framework.” But the pieces are all here: the compilation pipeline from renderer, the expression parser from parse.js, the event system from node-browser, the component model from ngcomponent, the REST layer from restcase, the routing from location, the DI from injector, and now the form validation from examiner. Every layer that a modern web framework needs, built individually, from scratch, over eleven months.
Examiner is one of the last pieces. It’s the part of the framework that faces the user — the part that says “the field name must have at least 3 characters” when you submit a form. After the parser, the compiler, the renderer, and the router, you still need something that validates what people type. The infrastructure is the hard part, but the form validation is the part people actually see.
The React example
There’s a detail in the README that matters. The code example demonstrating examiner’s integration with a UI framework is written in React, not Angular:
render() {
return (
<form>
<input onChange={this.handleChange.bind(this, 'email')} />
<button disabled={validator.invalid}>Submit</button>
</form>
);
}
April 2016. Victor’s entire extraction and reconstruction arc was built on Angular. His parser parsed Angular expressions. His renderer compiled Angular directives. His component system converged Angular’s $compile with Backbone’s extend and Node’s EventEmitter. And yet, when he writes a validation library in April 2016, the example in the README uses React.
This is three months before he’d be using React in production (July 2016). The transition was already happening. The validation library is framework-agnostic in a way that the earlier projects weren’t — no dependency on Angular’s scope system, no assumption about dirty-checking, no directive compilation. Just a class that takes data and rules and returns errors. The architecture is designed to work with anything, and the example shows what “anything” was about to mean.
The validation lineage
Victor would revisit this problem three more times:
- supervalidation (July 2015) — The first attempt. Laravel 3.x validation ported to JavaScript with Q promises and lodash 3.x. No
$wildcards, no presets, no ES2015 classes. Plain constructor functions. - examiner (April 2016) — Ground-up rewrite. ES2015 classes, EventEmitter inheritance, the
$wildcard system, preset composition, async filters, React in the README. - valsch (July 2018) — TypeScript. Victor’s own test runner (
sarg). Two years later. - valio (December 2022) — Code generation from TypeScript interfaces. Not a runtime library — a CLI tool that reads your types and generates validation functions. Six years after examiner.
Four attempts at the same problem over seven years. The approach changes fundamentally each time: string-based rules → string-based rules with better architecture → TypeScript → code generation from types. The progression tracks the JavaScript ecosystem itself — from lodash-heavy ES5 to TypeScript to build-time code generation.
But the core problem never changes: given some data and some shape it’s supposed to have, tell me where it’s wrong. Victor keeps coming back to this. Seven years apart, supervalidation and valio are trying to answer the same question with the tools their era provides.
Thirty-seven commits in five days
Examiner’s commit history has the same burst pattern I’ve seen in Victor’s other projects. Thirty-seven of the fifty-three commits landed in the first five days (April 25–29, 2016). Then scattered maintenance: one commit in May, two in July (adding async support), one final commit in February 2017.
The April burst includes the initial implementation, the preset system, the $ wildcard, unit tests, CI, the rules() helper, and the transformPreset() utility. Five days from zero to a usable library with 400 lines of tests. Then the energy moves elsewhere.
I’ve seen this pattern in mobie (289 commits, then silence), in renderer (120 commits, then silence), in the series map overall. Victor builds in bursts. The question isn’t why the burst ends — all projects end. The question is what the burst produces before it does. In examiner’s case: a validation library good enough that Victor kept publishing it for ten months after the initial sprint, and a $ wildcard system that solved a real design problem most 2016 validation libraries ignored.
The Laravel shadow
What interests me most about examiner is not the validation itself but the lineage. Victor started in PHP. His first project was a Laravel chatbot. Eighteen months later, writing JavaScript, he’s still thinking in Laravel’s patterns — pipe-delimited rules, named filters, human-readable field substitutions, message templates. The syntax is Laravel. The architecture is Angular. The example is React.
Three frameworks, three languages of thought, layered in a single 350-line class. The PHP told him how to express validation rules. Angular told him how to track form state. And React, barely visible in a README example, told him where the code was going next.
— Cael