Metadata
All annotations from .as files are preserved as runtime metadata on generated types. This is what makes Atscript more than just a type system — annotations like labels, descriptions, and validation rules are accessible in your TypeScript code.
Accessing Metadata
Every generated type has a metadata field — a typed Map keyed by annotation names:
import { Product } from './product.as'
// Top-level annotations (on the interface itself)
Product.metadata.get('meta.description') // 'A product in the catalog'
// Property-level annotations
const nameProp = Product.type.props.get('name')
nameProp?.metadata.get('meta.label') // 'Product Name'
nameProp?.metadata.get('expect.minLength') // { length: 3, message?: string }2
3
4
5
6
7
8
9
The metadata map is typed via the global AtscriptMetadata interface, so you get IntelliSense for annotation keys and their value types.
Iterating Properties
Walk all properties and their metadata using the props map:
import { Product } from './product.as'
for (const [key, prop] of Product.type.props.entries()) {
const label = prop.metadata.get('meta.label') || key
const required = !prop.optional
console.log(`${label} (${key}): ${required ? 'required' : 'optional'}`)
}2
3
4
5
6
7
Tags
Semantic types like string.email or number.positive produce tags that are available at runtime:
import { User } from './user.as'
const emailProp = User.type.props.get('email')
emailProp?.type.tags // Set { 'email', 'string' }2
3
4
Tags let you make runtime decisions based on semantic types — for example, rendering an email input vs a plain text input.
Nested Types
Every nested node — array element, union member, nested-object property — is itself a TAtscriptAnnotatedType with its own .metadata.get(...). Reach into the right field on the parent (type.of for arrays, type.items for unions/intersections/tuples, type.props.get(...) for objects) and call metadata.get on what you find:
import { User } from './models.as'
const addressProp = User.type.props.get('address')
if (addressProp?.type.kind === 'object') {
const cityProp = addressProp.type.props.get('city')
cityProp?.metadata.get('meta.label')
}2
3
4
5
6
7
Use forAnnotatedType() to traverse nested types generically — see Type Traversal for recursive walking patterns and practical examples.
Practical Example: Build A Field List
import { Product } from './product.as'
function buildFieldList() {
if (Product.type.kind !== 'object') return []
return Array.from(Product.type.props.entries()).map(([key, prop]) => ({
name: key,
label: prop.metadata.get('meta.label') || key,
required: !prop.optional,
placeholder: prop.metadata.get('ui.placeholder'),
sensitive: prop.metadata.get('meta.sensitive') || false,
readonly: prop.metadata.get('meta.readonly') || false,
}))
}
const fields = buildFieldList()
// [{ name: 'name', label: 'Product Name', required: true, ... }, ...]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
For more advanced traversal patterns — recursive walking, flattening nested types, collecting metadata across the type tree — see Type Traversal.
Next Steps
- Validation — annotations drive validation rules automatically
- Type Definitions — the annotated type system in depth
- Serialization — serialize types with metadata for transport