Schema derivation
Nola derives JSON Schema from extractor types, contextual parameter types and exported types in .tsi files. It uses the TypeScript checker’s resolved type, so aliases, imported types, utility types such as Partial<User> and inherited interfaces derive like their equivalent inline types.
Coverage
Section titled “Coverage”| TypeScript | JSON Schema |
|---|---|
string, number, boolean |
{ type: "string" } … |
"a" | "b", string enums |
{ type: "string", enum: ["a", "b"] } |
1 | 2, true, numeric enums |
{ const: 1 } inside anyOf |
T | null |
{ anyOf: [T, { type: "null" }] } |
A | B (objects, scalars, mixed) |
{ anyOf: [A, B] } — a shared literal key makes it a discriminated union for validation |
T[], ReadonlyArray<T> |
{ type: "array", items: T } |
[string, number?] |
{ type: "array", prefixItems: […], items: false, minItems, maxItems } |
{ a: string; b?: number }, interface, type aliases |
{ type: "object", properties, required, additionalProperties: false } |
interface X extends Y, A & B |
the flattened members |
Partial, Required, Pick, Omit, other mapped types |
the resulting members |
Record<string, T>, { [k: string]: T } |
{ type: "object", additionalProperties: T } |
generic types applied with arguments (Box<number>) |
the instantiated members |
Date |
{ type: "string", format: "date-time" }, revived to a Date |
| recursive types | $defs + $ref |
| types from another file or from a package | derived where they are declared; a .tsi or a view carries a named type’s value |
| JSDoc on a member | description |
JSDoc constraint tags (@format, @minimum, @minItems, …) |
the keyword — see Constraints |
A .tsi file’s own named types are referenced by name ($defs entries carry the declared name); a type imported from another project file is referenced as <module>#<Name>, so two files may export a Node each.
Constraints
Section titled “Constraints”Add constraints with JSDoc tags on a property signature or type alias. Each tag maps to a JSON Schema keyword. Nola includes the constraint in the schema and checks it during validation; failures appear in correction requests and validate results.
| Tag | Applies to | Schema | Validation message on failure |
|---|---|---|---|
@minLength n / @maxLength n |
strings | minLength / maxLength |
expected at least 3 characters, got 1 |
@pattern re |
strings | pattern (the rest of the tag text, a JavaScript regular expression without flags) |
expected a string matching ^[a-z]+$, got "A B" |
@format name |
strings | format |
expected a valid email, got "x" |
@minimum n / @maximum n |
numbers | minimum / maximum |
expected a number ≥ 13, got 9 |
@exclusiveMinimum n / @exclusiveMaximum n |
numbers | exclusiveMinimum / exclusiveMaximum |
expected a number > 0, got 0 |
@multipleOf n |
numbers | multipleOf (decided exactly on decimals) |
expected a multiple of 0.01, got 1.005 |
@integer |
numbers | type: "integer" |
expected an integer, got 1.5 |
@minItems n / @maxItems n |
arrays and tuples | minItems / maxItems |
expected at least 1 item, got 0 |
@uniqueItems |
arrays and tuples | uniqueItems (items compared as canonical JSON) |
expected unique items, found a duplicate at index 2 |
Formats: date-time, date, time, email, uri, uuid, ipv4, ipv6, hostname. Lengths count UTF-16 code units, as JavaScript’s length does.
Rules:
- Kind. “Strings” includes string-literal unions and string enums; “numbers” includes numeric literals. The kind is decided on the non-null, non-undefined part of the type, so
@minLength 1onstring | nullconstrains the string and letsnullthrough. A tag on any other kind — an object, a boolean, aDate, a mixed union such asstring | number— isNOLA2012. - Values. Numeric tags need a finite number;
@patternand@formatneed a value;@integerand@uniqueItemstake none. A missing, malformed or repeated tag, or an unknown format, isNOLA2012. - Aliases. Tags on
type Id = stringlive withId; a propertyid: Idmay add its own. Tags on aninterfaceorclassdeclaration itself are not constraints and are ignored, as are unrelated JSDoc tags (@see,@deprecated,@param). - Dialects.
~standard.jsonSchemarenders the keywords for every target; OpenAPI 3.0 getsexclusiveMinimum: truebesideminimum, and a constrained recursive reference is wrapped inallOfwhere the dialect ignores siblings of$ref.
Decision types
Section titled “Decision types”Decision types (Choice<{ … }>, Scale<[ … ]> and Prob<{ … }>) retain their criteria through Partial, Pick, Omit and generic instantiation.
Their schemas describe the answer’s structure and include an x-nola-decision keyword with the question: { kind: "choice", criteria }, { kind: "scale", levels } or { kind: "prob", criteria? }. Decision providers read this keyword, and every supported schema dialect preserves it.
Validation checks that the label is one of the supplied choices, the distribution sums to 1 within 1e-6, the score is between 0 and levels.length - 1, and a probability is between 0 and 1. The runtime adds levels to a scale answer.
Malformed criteria produce NOLA2015 wherever the type is used. A JSDoc constraint tag on Prob produces NOLA2012.
Not derivable
Section titled “Not derivable”Nola cannot derive schemas for Map, Set, Promise, RegExp, functions, methods, symbol, bigint, unknown, any or never. Tuple rest elements, number index signatures and generic types with unresolved arguments are also unsupported. Instantiate a generic, such as Box<number>, before using it.
An extractor with an unsupported type produces NOLA2002. Contextual parameters follow the configured policy. An unsupported exported type becomes an UnsupportedType value; using it produces a compile-time error with the reason.
Validation
Section titled “Validation”Nola validates a reply against the resolved schema and reports all issues it finds. The correction request includes the full list, as does validate on a type value.
For a discriminated union, validation uses the shared literal key to select a branch and reports that branch’s issues. Other unions accept the first matching member; if none matches, validation reports one issue listing the alternatives. Valid Date fields are converted back to Date objects.
Where derivation runs
Section titled “Where derivation runs”Schema derivation runs in nola run, nola build, nola check, the bundler plugins and the Turbopack loader. Each process uses one TypeScript language service, configured by the nearest tsconfig.json or strict NodeNext defaults if there is none.
The editor runs the same derivation during diagnostics checks and reports unsupported types at their source location. Hover and completion do not wait for derivation.