Schema Derivation
JsonSchema.output derives a JSON Schema from a Spec or Plan without executing it. The schema reflects the output structure, including types, required fields, and nested objects.
Basic usage
Section titled “Basic usage”import { JsonSchema } from '@origints/core'
// From a Planconst schema = JsonSchema.output(plan)// { type: 'object', properties: { name: { type: 'string' }, age: { type: 'number' } }, required: ['name', 'age'] }import { extract } from '@origints/core'
// From a Specconst spec = extract($ => ({ name: $.get('name').string(), age: $.get('age').number(),}))const schema = JsonSchema.output(spec)Options
Section titled “Options”JsonSchema.output(plan, { draft: 'draft-07', id: 'https://example.com/user.schema.json', title: 'User', description: 'A user record', deduplicate: true,})| Option | Effect |
|---|---|
draft | JSON Schema dialect: 'draft-07', '2019-09', or '2020-12' (default). Draft-07 uses definitions; 2019-09 and 2020-12 use $defs. |
id | Sets $id on root schema |
title | Sets title on root schema |
description | Sets description on root schema |
deduplicate | Extracts repeated sub-schemas into $defs/definitions with $ref pointers |
Property metadata
Section titled “Property metadata”describe(path, meta) attaches annotations to an extraction. These appear in the derived JSON Schema.
.emit((out, $) => out .add('email', $.get('email').string()) .describe('email', { description: 'User email', format: 'email' }) .add('age', $.get('age').number()) .describe('age', { minimum: 0, maximum: 150 }))Supported OutputMeta fields: description, title, examples, deprecated, format, pattern, minimum, maximum, minLength, maxLength, enum.
Deduplication
Section titled “Deduplication”When deduplicate: true, repeated sub-schemas are extracted into $defs (or definitions for Draft-07) and replaced with $ref pointers. Definitions that contain other definitions use $ref internally — no inline duplication.
Naming strategy:
- Property key — the parent property name (e.g., property
home→ defHome) - Singularized array items — array item schemas use a singularized form (e.g.,
addresses→Address) - Property-set fallback — object schemas with ≤4 properties use PascalCase property names joined (e.g.,
NameAge) - Counter fallback —
Schema1,Schema2, etc.
Named definitions via $defName
Section titled “Named definitions via $defName”$defName promotes a sub-schema to $defs with a specific name, even if it appears only once and even without deduplicate: true. It can be specified in two places:
Emit-level
Section titled “Emit-level”Use .describe() on an output property. For array properties, $defName names the item schema, not the array itself. Emit-level hints resolve against the post-transform schema, so the definition includes any properties added by subsequent mapOut transforms (e.g., lookups).
.emit((out, $) => out .add('orders', $.get('orders').array(o => ({ ... }))) .describe('orders', { $defName: 'Order' }) // → orders.items becomes { $ref: '#/$defs/Order' })Transform-level
Section titled “Transform-level”Use the $defName option on output transforms that produce new named schema shapes: lookup, indexBy, groupBy, nest, and joinBy.
.mapOut($ => $.lookup(o => o.orders, { from: 'customers', localKey: 'customerId', foreignKey: 'cid', as: 'customer', cardinality: 'one', $defName: 'Customer',})).mapOut($ => $.indexBy(o => o.users, 'id', { $defName: 'User' })).mapOut($ => $.groupBy(o => o.items, 'cat', { $defName: 'Item' })).mapOut($ => $.nest('address', ['street', 'city'], { $defName: 'Address' }))| Transform | What $defName names |
|---|---|
lookup (one) | The source item schema (inside anyOf: [schema, null]) |
lookup (many) | The source item schema (inside items) |
lookup (merge) | Not applicable (fields are spread directly) |
indexBy | The item schema (inside additionalProperties) |
groupBy (record) | The item schema (inside additionalProperties.items) |
groupBy (entries) | The entry object schema {key, items} |
nest | The nested object schema |
joinBy | The joined item object schema |
When both emit-level and transform-level hints exist, they are merged. Transform-level hints take precedence for the same schema.
Spec kind mapping
Section titled “Spec kind mapping”| Spec | Schema |
|---|---|
extract (string/number/boolean/null/date/datetime) | { type: T } with optional default |
literal | { const: value } |
array | { type: 'array', items: ... } |
object | { type: 'object', properties: ..., required: [...] } |
match | Union of branch schemas via anyOf or type merge |
concat | { type: 'array', items: merged } |
try | Union of all fallback schemas |
map | Schema of the source spec |
guard | Schema of the source spec |
panic | {} (never produces output) |