pattern - domain knowledge as data
truecopy/pattern
Everything that varies by market, by issuer or by document family should be a value, not a branch. And a pattern off the wire is untrusted input applied to untrusted input.
Everything that varies by market, by issuer or by document family is a value, not a branch.
A value can be served by a backend, refined by whoever reads the documents, versioned, and swapped for another market without a deploy. A branch can only be changed by whoever can change the code — which in practice means a release, a review and a week.
import { compilePatterns, toRawPatterns, NEVER_MATCHES } from 'truecopy/pattern';
// Off the wire, as JSON
const raw = [{ source: 'solde\\s{0,3}(cr[ée]diteur|d[ée]biteur)', flags: 'i' }];
const patterns = compilePatterns(raw);
Regular expressions do not survive JSON, so they travel as { source, flags } and are rebuilt here.
Which is exactly where the danger is
A profile off the wire is untrusted input applied to untrusted input.
A pattern arrives from a backend and is then run, line by line, over a document somebody uploaded. Both ends are outside your control, and a catastrophically backtracking expression there is not slow — it is a tab that never comes back.
So compilePattern guards before it compiles:
- a source longer than a thousand characters is rejected;
- a nested quantifier — the shape that makes backtracking explode — is rejected.
And a rejected pattern compiles to NEVER_MATCHES, not to a throw. The feature degrades and the page stays alive. Throwing would take the whole reading down because one entry in a served profile was wrong.
Counting occurrences
import { countMatches } from 'truecopy/pattern';
countMatches(text, pattern); // how many times, bounded
Used by classify for the rule that one occurrence may be a quotation and two rarely are.
What this is not
It is not a place to put your vocabulary. The library never reads your patterns’ meaning — it compiles them, guards them and counts them. Which words belong to a statement and which to an invoice is yours, and it belongs in a profile you can change without asking anyone.