Interfaces & Types
Atscript provides TypeScript-like syntax with annotations and semantic types.
Start With Interfaces
Most .as files are just interfaces with a few annotations and semantic types:
export interface User {
id: string
name: string
age: number
isActive: boolean
}Use an interface when you want a named object shape that can later be imported, validated, and inspected at runtime.
Nest Objects Naturally
Inline nested objects work well when the nested shape is local to one model:
export interface User {
id: string
profile: {
name: string
avatar?: string
}
settings: {
theme: 'light' | 'dark'
}
}If you want to reuse a nested shape across files, give it its own interface or type alias and import it.
Use Type Aliases For Reusable Values
Type aliases are useful for named primitives, unions, and reusable constraints:
@expect.minLength 3
@expect.maxLength 20
export type Username = string
export type Status = 'pending' | 'success' | 'error'
export type ID = string | numberLike interfaces, exported type aliases also exist at runtime and can be validated.
Common Property Patterns
Optional Properties
export interface Config {
name: string
bio?: string
}Arrays, Tuples, And Literals
export interface Data {
tags: string[]
coords: [number, number]
status: 'pending' | 'done'
}Dynamic Keys
If a model needs open-ended keys, use wildcard or pattern properties:
export interface EnvConfig {
NODE_ENV: 'development' | 'production'
[/^PUBLIC_.*/]: string
}That is useful for configuration objects, custom metadata maps, or other flexible records.
Reuse Types By Reference
import { Address } from './address'
export interface User {
address: Address
friends: User[]
manager?: User
}You can reference other types, self-reference, and use arrays of references naturally.
Advanced Composition
Interface Extends
interface BaseEntity {
id: string
createdAt: string.isoDate
}
interface Timestamped {
updatedAt: string
}
export interface Post extends BaseEntity, Timestamped {
title: string
body: string
}Rules:
- Own properties are added to the inherited ones
- Prop-level annotations are inherited from parents
- Interface-level annotations are not inherited
- Overriding a parent property in a child is not allowed
- Self-extends and circular extends are detected as errors
Intersection Types
interface Timestamped {
createdAt: string
}
interface Post {
title: string
} & TimestampedIntersections are useful when you want to combine types inline instead of declaring a new parent interface.
Practical Example
import { Address } from './address'
export interface User {
id: string.uuid
username: string.required
email: string.email
profile: {
displayName: string
bio?: string
[/^social_.*/]: string
}
addresses: Address[]
status: 'active' | 'inactive' | 'pending'
createdAt: string.isoDate
}TypeScript Usage
In TypeScript, use type aliases as both types and validators:
import { Username } from './types.as'
const name: Username = 'john_doe'
const validator = Username.validator()
if (validator.validate(input, true)) {
// input is Username
}Next Steps
- Imports & Exports — Module system
- Primitives — Semantic types
- Annotations — Metadata system
- Ad-hoc Annotations — Annotate existing types without modification