Typed extraction
To extract structured data, define the result with a TypeScript type and use that type in an ask. This guide starts with a flat object, then adds nested data, optional fields, shared types and constraints.
1. A flat object
Section titled “1. A flat object”export interface Person { name: string; age: number; employer: string; job: string;}
export infer function extractPerson(.message: string) { const person = ask `the person described in the text`: Person; return person;}import { extractPerson } from "./person.tsi";
const person = await extractPerson( "Alice Smith, 32, is a staff engineer at Acme Corp working on distributed systems.",);console.log(JSON.stringify(person));// {"name":"Alice Smith","age":32,"employer":"Acme Corp","job":"staff engineer"}Nola derives a JSON Schema from Person at compile time and validates the model’s reply against it at runtime. The returned person has type Person.
2. Nested arrays and descriptions
Section titled “2. Nested arrays and descriptions”export interface Education { school: string; degree: string; /** graduation year */ year: number;}
export interface Resume { name: string; email: string; /** one entry per position, most recent first */ experience: string[]; skills: string[]; education: Education[];}
export infer function extractResume(.message: string) { return ask `the candidate's resume data`: Resume;}Use arrays and nested object types as you would in other TypeScript code. A JSDoc comment on a member becomes its description in the schema, so the model receives field-specific guidance such as “most recent first” alongside the field.
3. Optional fields and same-file references
Section titled “3. Optional fields and same-file references”export interface LineItem { description: string; quantity: number; /** price per unit in USD */ unitPrice: number;}
export interface Invoice { invoiceNumber: string; issuedTo: string; lineItems: LineItem[]; /** grand total in USD */ total: number; /** ISO date; omit when the document has none */ dueDate?: string;}
export infer function extractInvoice(.document: string) { return ask `the invoice data from the document`: Invoice;}An optional member such as dueDate? is left out of the schema’s required list. Use its JSDoc description to explain when it should be omitted. Nola also derives the nested LineItem type referenced by Invoice.
4. Types from another file
Section titled “4. Types from another file”Shared types can stay in plain .ts modules. Import them with import type and, in a NodeNext project, the .js specifier:
export interface Person { name: string; age: number;}import type { Person } from "./models.js";
export infer function extractPerson(.text: string) { return ask `the person described in the text`: Person;}Nola resolves Person from models.ts and derives its schema. TypeScript interop explains the module view used for this. You can also import the derived type as a runtime value for validation and schema access; see Types as values.
5. Constraints
Section titled “5. Constraints”Add JSDoc tags when a TypeScript type needs a more specific constraint. For example, @format email requires an email-shaped string and @minimum 13 sets a numeric lower bound. Nola adds these keywords to the schema and checks them when validating replies. A failed check supplies the error message for the correction retry.
export interface Signup { /** @format email */ email: string; /** the public handle @minLength 3 @pattern ^[a-z0-9_]+$ */ handle: string; /** @integer @minimum 13 */ age: number; /** @minItems 1 @uniqueItems */ interests: string[];}
export infer function parseSignup(.text: string) { return ask `the signup form described in the text`: Signup;}Descriptions and tags can share a comment. See Constraints for supported keywords and formats. A tag on an incompatible type raises NOLA2012 at compile time.
Mixing in plain TypeScript
Section titled “Mixing in plain TypeScript”After extraction, call ordinary TypeScript helpers to process the result. This example imports a helper with the NodeNext ./x.js specifier:
import type { Person } from "./person.tsi";
export function normalizePerson(person: Person): Person { return { ...person, name: person.name.trim(), employer: person.employer.trim() };}import { normalizePerson } from "./format.js";import type { Person } from "./person.tsi";
export infer function extractPersonNormalized(.message: string) { const person = ask `the person described in the text`: Person; return normalizePerson(person);}Instruction wording tips
Section titled “Instruction wording tips”- Describe the result you need.
the invoice data from the documentis enough to identify the task; the type supplies its structure. - Use types for structure. Express optional fields, label sets and nesting in the type so Nola can derive and validate them.
- Keep field guidance with the field. Use JSDoc descriptions for instructions and supported tags, such as
/** @format email */, for constraints that validation should enforce. - Group related results deliberately. Separate extractors can make independent tasks easier to follow, while one object extractor retrieves related fields in a single call. For fixed label sets, see Classification.
Next: Classification