jit
ReferenceOperators

String operators

Length, formats, normalization, document masks, sanitization and ISO string checks.

Length and membership

OperatorAcceptsExample
.min(n)length at least nusername minimum
.max(n)length at most ndatabase column limit
.length(n)exact lengthcountry code, fixed token
.oneOf(values)one exact stringsmall status allow-list
.startsWith(text)fixed prefixnamespaced identifier
.endsWith(text)fixed suffixfile extension convention
.includes(text)required substringprotocol 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 onlyProduces transformed parse output
lowercase, uppercasetoLowerCase, toUpperCase
format checkstrim, 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)-only httpUrl();
  • uuid(version?) and permissive UUID-like guid();
  • cuid, cuid2, ulid, xid, ksuid, nanoid;
  • jwt, hostname, domain, E.164 e164;
  • ipv4, ipv6, cidrv4, cidrv6, mac(delimiter?);
  • base64, base64url, hex, and digest(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.

On this page