Types as values
Nola can turn an exported TypeScript type into a runtime value for schema generation and validation. It uses the same derivation as extractors, so you can reuse a type without writing a separate schema.
The value
Section titled “The value”export interface User { name: string; email?: string; role: "admin" | "member";}// api.ts — plain TypeScriptimport { User } from "./user.tsi";
declare const body: unknown;
const schema = User.toJsonSchema(); // { type: "object", properties: …, required: ["name", "role"], … }const u: User = User.parse(body); // the interface AND the value share one nameEach export type and export interface in a .tsi file also exports an InferType<T> value with four members: toJsonSchema, validate, parse and ~standard. Use import { type User } when you only need the type.
Non-exported types do not get runtime values. Enums already have values and are left unchanged. If an exported type shares a name with a const, let, function, class or enum, Nola raises NOLA2011.
Validate and parse
Section titled “Validate and parse”validate(value) returns { ok: true, value } on success, with date fields converted to Date instances. On failure, it returns { ok: false, issues }. Each issue has a path: (string | number)[] and a message.
parse(value) returns the validated value or throws NolaValidationError (NOLA3016) with the same issues.
const checked = User.validate(input);if (!checked.ok) { for (const issue of checked.issues) console.warn(issue.path.join("."), issue.message);}Validation checks the derived schema, which may cover less than the original TypeScript type. parse returns a value typed as T, but that annotation does not add runtime checks. See derivation policies.
Constraints
Section titled “Constraints”JSDoc tags add constraints that a TypeScript type alone cannot express, such as a minimum string length. They map to JSON Schema keywords, so the model receives the constraints and validate checks them. Validation reports an issue for each violated constraint:
export interface Signup { /** @format email */ email: string; /** the public handle @minLength 3 @maxLength 32 @pattern ^[a-z0-9_]+$ */ handle: string; /** @integer @minimum 13 @maximum 120 */ age: number; /** @minItems 1 @maxItems 10 @uniqueItems */ tags: string[]; /** @minLength 1 */ note: string | null; // constrains the string; null still passes}
/** @format uuid */export type Id = string; // travels with every use of IdString constraints include @minLength, @maxLength, @pattern and @format. Supported formats are date-time, date, time, email, uri, uuid, ipv4, ipv6 and hostname. Numbers support @minimum, @maximum, @exclusiveMinimum, @exclusiveMaximum, @multipleOf and @integer. Arrays support @minItems, @maxItems and @uniqueItems.
A tag applies to the non-null part of a type. Tags on an alias follow its uses, and property tags add further constraints. You can include description text alongside tags. Invalid, repeated or inapplicable tags raise NOLA2012. See the full constraint table.
Standard Schema
Section titled “Standard Schema”User["~standard"] is a Standard Schema v1 object (vendor: "nola"), so any library that accepts Standard Schema — form libraries, routers, agent SDKs — accepts a Nola type directly, no adapter. The same object also implements Standard JSON Schema: jsonSchema.input({ target }) and jsonSchema.output({ target }) return the schema in the dialect the consumer names — OpenAPI generators and tool-definition builders look for exactly this member.
User["~standard"].jsonSchema.input({ target: "draft-2020-12" }); // toJsonSchema() itselfUser["~standard"].jsonSchema.input({ target: "draft-07" }); // definitions/$ref, items[] for tuplesUser["~standard"].jsonSchema.output({ target: "openapi-3.0" }); // nullable: true, enum for literals, no tuplesThree targets are supported: draft-2020-12, draft-07 and openapi-3.0; any other target throws NOLA3017, as the spec asks. input and output return the same document: the one place the accepted and returned values differ is Date (an ISO string in, a Date instance out), and JSON Schema has no way to describe the instance, so the wire shape is the answer on both sides. Every document is plain JSON Schema — no vendor keywords.
Views of plain TypeScript
Section titled “Views of plain TypeScript”./models.tsi means the Nola file models.tsi when it exists. Otherwise it is the view of models.ts (then models.d.ts): the same module re-exported, plus a value for every exported type alias and interface. A type that lives in ordinary TypeScript gets the same treatment without moving it — a project can use Nola for this alone, with no .tsi file and no model (Types without a model):
// plain TypeScript, untouchedexport interface Person { name: string; age: number;}
export function helper(): number { return 1;}import { Person, helper } from "./models.tsi"; // no models.tsi on disk: the view of models.ts
const schema = Person.toJsonSchema();helper(); // one module instance — the same function as through "./models.js"If neither a Nola file nor a TypeScript module exists for a .tsi import, Nola raises NOLA2007. Generated code uses the same view resolution: a type imported from ./models.js gets its runtime schema through ./models.tsi.
Siblings: x.ts and x.tsi
Section titled “Siblings: x.ts and x.tsi”You can have both x.ts and x.tsi, but ./x.tsi always selects the Nola file. nola build and nola check warn when both exist. Use distinct basenames to make imports unambiguous.
Bundlers and Turbopack
Section titled “Bundlers and Turbopack”The Vite, webpack, Rollup, Rolldown, esbuild and Rspack plugins provide views as virtual modules. They also write x.d.tsi.ts beside viewed sources so framework type checks can resolve the import.
The Turbopack integration inlines generated view imports into lowered modules. It supports real .tsi files, but a direct ./models.tsi view import from plain .ts is unsupported. Use webpack mode for that case.
Limits
Section titled “Limits”Derivation covers what extractors cover — the type as the TypeScript checker resolves it: unions, Partial/Pick/Omit, extends, intersections, tuples, records, generics applied with arguments, and types from other files or packages (see Schema derivation). Map, Set, Promise, functions and a generic declaration used without arguments are not derivable: such an export becomes an UnsupportedType<reason> value — calling a method on it is a compile-time error carrying the reason.
Next: TypeScript interop