String operators
Length, formats, normalization, document masks, sanitization and ISO string checks.
Length and membership
| Operator | Accepts | Example |
|---|---|---|
.min(n) | length at least n | username minimum |
.max(n) | length at most n | database column limit |
.length(n) | exact length | country code, fixed token |
.oneOf(values) | one exact string | small status allow-list |
.startsWith(text) | fixed prefix | namespaced identifier |
.endsWith(text) | fixed suffix | file extension convention |
.includes(text) | required substring | protocol marker |
const TenantKey = JIT.string()
.startsWith("tenant:")
.includes(":user:")
.endsWith(":v1");Literal lengths and allow-lists participate in strict default checking. Dynamic strings still require runtime validation because TypeScript cannot know their contents.
Validation versus transformation
| Validation only | Produces transformed parse output |
|---|---|
lowercase, uppercase | toLowerCase, toUpperCase |
| format checks | trim, normalize |
regex, startsWith, etc. | noEmpty, format, sanitize |
const CanonicalEmail = JIT.string().trim().toLowerCase().email();
CanonicalEmail.is(" Ada@Example.com "); // false: not canonical yet
CanonicalEmail.parse(" Ada@Example.com "); // "ada@example.com"Parsers return the original input reference when no node requires rebuilding. Transforms make the build path explicit and may allocate the changed string or parent object.
Web and identifiers
Built-ins include:
email(customPattern?, message?);url()and HTTP(S)-onlyhttpUrl();uuid(version?)and permissive UUID-likeguid();cuid,cuid2,ulid,xid,ksuid,nanoid;jwt,hostname,domain, E.164e164;ipv4,ipv6,cidrv4,cidrv6,mac(delimiter?);base64,base64url,hex, anddigest(algorithm, encoding);- Unicode
emoji.
const TraceId = JIT.string().ulid("invalid trace id");
const AssetHash = JIT.string().digest("sha256", "base64url");
const Webhook = JIT.string().httpUrl();Built-ins are more portable than an opaque refinement: regex bindings are external in runtime JIT and safely emitted in AOT.
Custom formats
const Slug = JIT.string().stringFormat(
"slug",
/^[a-z0-9]+(?:-[a-z0-9]+)*$/,
"expected a lowercase slug",
);Use .regex() for a local check. Use .stringFormat(name, pattern) when the
issue's expected value should expose a stable format name.
Avoid regexes with catastrophic backtracking at untrusted boundaries. JIT can move schema traversal out of the hot path, but it cannot make an unsafe regular expression safe.
Empty HTML form values
const Filters = JIT.object({
search: JIT.string().noEmpty().optional(),
locale: JIT.string().noEmpty().default("pt-BR"),
});noEmpty maps "" to missing input before optional/default handling. It is
ideal for form controls and URL query parameters. Do not use it for patches or
documents where clearing a value to an empty string is meaningful.
Brazilian documents and masks
const Customer = JIT.object({
cpf: JIT.string().cpf(),
cnpj: JIT.string().cnpj(),
phone: JIT.string().phoneBR(),
});
const StrictCPF = JIT.string().format("###.###.###-##", { mode: "strict" });
const formatCPF = JIT.format(JIT.string().format("###.###.###-##")).compile();The format grammar is typed. # is a digit placeholder; spaces and supported
punctuation are literals. Transform mode strips non-digits by default and only
constructs output in parse operations. Strict mode requires every literal and
digit position to already match. JIT.format(schema).compile() creates a pure
specialized formatter without the validator runtime.
Store/index canonical digits when possible and format at a presentation or serialization boundary. Comparing masked strings increases cardinality and can defeat binary dictionary/index reuse.
Sanitization
.sanitize() defaults to the fast text preset: executable element blocks
are removed, remaining tags are stripped and stray angle brackets are escaped.
The same specialized chain runs inside parse, while JIT.sanitize(schema)
creates a dedicated immutable sanitizer over only the marked paths.
const Input = JIT.object({
title: JIT.string().sanitize(), // same as "text"
visibleMarkup: JIT.string().sanitize("htmlEscape"),
column: JIT.string().sanitize("sqlIdentifier"),
uploadName: JIT.string().sanitize("pathSegment"),
});Use preset: "none" to start with no implicit rule and compose an explicit
policy. Allowed HTML tags are canonicalized and keep no attributes; dangerous
elements such as script, style, iframe, object and embed cannot be
allowed.
const Body = JIT.string().sanitize({
preset: "none",
html: { mode: "allow", tags: ["b", "em", "code"] },
controls: "remove",
normalize: "NFC",
trim: true,
maxLength: 10_000,
patterns: [{ pattern: /javascript:/gi, replacement: "" }],
});The compiler emits only those replacements into the generated function. There is no policy interpreter, options object or package dependency in the hot path, and untouched object branches retain their references.
sqlIdentifier is an allow-list cleaner for dynamic identifiers such as a
validated report column. It is not SQL-injection protection for values. Always
send values through prepared/parameterized queries. Likewise, use argument
arrays for child processes instead of trying to sanitize a shell command.
For rich HTML that must preserve links, attributes, CSS or embedded media, use a dedicated HTML parser/sanitizer at that boundary. The built-in allow mode is intentionally restricted to simple formatting tags.
ISO aliases
The following remain compatible:
JIT.string().date();
JIT.string().time({ precision: 0 });
JIT.string().datetime({ offset: true });
JIT.string().duration();Prefer the grouped API in new code:
JIT.iso.date();
JIT.iso.time({ precision: 0 });
JIT.iso.datetime({ offset: true });
JIT.iso.duration();Both compile to the same check nodes and infer string.