declaration expressionage string // Valid
age string // Same as above
age string // Tab separator - also validUser {
id string
email string
createdAt string
}// syntax. Everything after // is ignored by the parser.// This is a standalone comment
age string // Age of the user
age:HumanAge number // Age between 3 and 120
// Comment with leading spaces
status string(active|inactive) // User account status
// Multiple standalone comments
// Can appear consecutively
User {
id string
}' or double ") serve two purposes in SPECML:"Account Name" string
'User Email' string<isEmail>
"field-with-dashes" numberAccount Name would be split at the space, treating Account as the declaration and Name as the expression.#, :, [], ?) as literal characters rather than syntax:person#Human // References the Human type
"person#Human" string // Literal field name containing #
field:output string // Creates an alias
"field:output" string // Literal field name containing :"..." or '...')"Account 'Statement'" or 'Account "Statement"'"Account \"Escaped\"""First Name" string<minLength:3> // Quoted declaration
pattern string<matches:"^[A-Z]{2,5}$"> // Quoted in modifier value
mode string(read|write|"read|write") // Quoted in enum valueage number<min:10|max:100> // Compact
age number < min:10 | max:100 > // Spaced out
age number < min:10 | max:100 > // Very spaced
status string(active|inactive) // Compact
status string ( active | inactive ) // Spaced
tags[] string // Compact
tags [] string // Spaced
tags [ ] string // More spacedmetadata // Can be any type
config // Can be any type
data // Can be any type// User authentication schema
// This schema defines the core user model
User {
id string<ulid> // Unique identifier
email string<isEmail|lowercase> // Must be valid email
// Username constraints
username string<minLength:3> // At least 3 characters
age number<min:13> // Minimum age requirement
status string(active|inactive) // Account status
}
// End of User schemaAPIResponse {
"Content-Type" string // HTTP header with dash
"X-Request-ID" string<ulid> // Header with special chars
"user#id" string // Field name with # character
"created:at" string<isISO> // Field name with : character
}Product {
price number<min:0|max:1000000>
// Same as above, just more spaced for readability
discountPrice number < min:0 | max:1000000 >
// Arrays with spacing
tags[] string
categories [ ] string
// Enums with spacing
status string(active|inactive|archived)
priority string ( low | medium | high )
}// These fields have names that look like SPECML syntax
Config {
"field#ref" string // # is literal, not a reference
"input:output" string // : is literal, not an alias
"items[]" string // [] is literal, not an array
"optional?" boolean // ? is literal, not optional marker
}
## Basic Declarations
### Simple Names
The `baseName` is the foundation of every declaration. At its simplest, it's an identifier for your field or type.
```specml
name
email
age
firstName
user_id_-#baseName is the foundation of every declaration. At its simplest, it's an identifier for your field or type.name
email
age
firstName
user_id@.firstName
user_id
user-name
-field
--
field.name
user@domain
$price
#tag
123account
9total{ "key": value }), it's valid as a baseName in SPECML."First Name"
"User Email"
"field-with-special-chars!"
"user@domain.com"
"price (USD)"
"field/path":, [, ], ?, {, })// Requires quotes - contains spaces
"Account Name" string
"First Name" string
// Requires quotes - contains parser syntax characters
"field:output" string // Without quotes, : would be interpreted as alias syntax
"items[]" string // Without quotes, [] would be interpreted as array syntax
"optional?" string // Without quotes, ? would be interpreted as optional syntax
"object{}" string // Without quotes, {} would be interpreted as block syntax
// Requires quotes - unicode characters
"用户名" string // Chinese characters
"prénom" string // Accented characters
"👤 User" string // Emoji#, ., and @ are allowed in baseNames without quotes and have no special meaning when used this way:// These are just literal characters in the baseName
field.name string
user@domain string
element#id string
$amount numberfield.name is a valid baseName in SPECML, it does NOT represent nested object notation. It's simply a field named field.name. This is different from some other schema languages where dots indicate nesting.# is used as part of SPECML syntax (for references), it must be outside quotes:owner#Person // References Person type
"owner#Person" string // Literal field name containing #UserName and username are distinct fields:User {
UserName string // Different from username
username string // Different from UserName
EMAIL string // Different from email
email string // Different from EMAIL
}123 string
456 number
789account string
9total numbertrue boolean
false boolean
null string
root object
type string"..." or '...'"Account 'Name'" or 'Account "Name"'"Account \"Name\"""Account Name" string // Double quotes
'Account Name' string // Single quotes
"Account 'Primary' Name" string // Mixed quotes
'Account "Primary" Name' string // Mixed quotes
"Account \"Escaped\" Name" string // Escaped quotes| Character | Without Quotes | With Quotes | Notes |
|---|---|---|---|
| Space | ❌ Invalid | ✅ Valid | Causes lexer split |
_ | ✅ Valid | ✅ Valid | Common in identifiers |
- | ✅ Valid | ✅ Valid | Common in kebab-case |
. | ✅ Valid | ✅ Valid | No special meaning in baseName |
# | ✅ Valid | ✅ Valid | Only special when used as reference syntax |
$ | ✅ Valid | ✅ Valid | Common in variable names |
@ | ✅ Valid | ✅ Valid | No special meaning |
: | ❌ Invalid | ✅ Valid | Used for alias syntax |
[ ] | ❌ Invalid | ✅ Valid | Used for array syntax |
? | ❌ Invalid | ✅ Valid | Used for optional syntax |
{ } | ❌ Invalid | ✅ Valid | Used for block syntax |
/ | ❌ Invalid | ✅ Valid | Could confuse parser |
\ | ❌ Invalid | ✅ Valid | Used for escaping |
User {
id string
email string
age number
firstName string
lastName string
isActive boolean
}Product {
product_id string
product-name string
unit_price number
in-stock boolean
created_at string
updated-at string
-priority number
-- string
_internal string
}Config {
api.endpoint string
api.key string
user@domain string
$price number
#identifier string
}APIResponse {
"Content-Type" string
"X-Request-ID" string
"Cache-Control" string
}
Invoice {
"Line Item 1" string
"Line Item 2" string
"Total (USD)" number
"Tax Rate %" number
}
Multilingual {
"用户名" string // Chinese
"nom d'utilisateur" string // French
"Benutzername" string // German
"👤 Profile" string // Emoji
}Schema {
// Using SPECML syntax as literal characters
"field:alias" string // : is literal
"array[]" string // [] is literal
"optional?" string // ? is literal
"reference#type" string // # is literal
"object{}" string // {} is literal
}ErrorCodes {
100 string
200 string
404 string
500 string
}User {
ID string // Different from id
id string // Different from ID
Email string // Different from email
email string // Different from Email
}Company {
// Simple names
name string
industry string
// With underscores
employee_count number
// With hyphens
tax-id string
// With dots
domain.name string
// Requiring quotes
"Legal Name" string
"Founded (Year)" number
"# of Offices" number
}_, -, ., #, $, @:, [], ?, {}), and unicodeUserName ≠ username## symbol creates a reference to another type definition, enabling type reuse and composition throughout your schema.#TypeName after the baseName:Person {
name string<minLength:1>
age number<min:18>
email string<isEmail>
}
Employee {
employeeId string
person#Person
}person field in Employee references the Person type, inheriting all its properties and constraints.person will have name, age, and email fields with all their constraints.// All types must be defined in the same file
Address {
street string
city string
}
User {
homeAddress#Address // References Address defined in this file
}User {
address#Address // Address is defined later - this is valid
}
Address {
street string
city string
zipCode string
}// Recommended: Define first, reference later
Address {
street string
city string
zipCode string
}
User {
address#Address
}# symbols. Resolution is hierarchical:Company {
Locations {
Headquarters {
city string
country string
}
}
}
Branch {
location#Company#Locations#Headquarters
}CompanyCompany, it finds LocationsLocations, it finds Headquarterslocation field inherits all properties from Headquarters#Type1#Type2#Type3#Type4...// Error: Type2 not found in Type1
field#Type1#Type2# as Literal Character# as a regular character in a baseName without reference meaning, enclose it in quotes:"currentAccount#number" string // Just a property name, not a reference
"field#123" number // # is literal
"user#id" string // # is literalbaseName#reference:alias[length]?Address {
street string
city string
}
User {
// Reference only
homeAddress#Address
// Reference + optional
workAddress#Address?
// Reference + array
previousAddresses#Address[]
// Reference + array + optional
vacationHomes#Address[]?
// Reference + alias
primary#Address:primaryAddress
// Reference + alias + array + optional
contacts#Address:mailingAddresses[]?
}{}, you're applying limited modifications to that reference, not extending it:Person {
name string<minLength:1>
age number<min:18>
title string(mr|mrs|miss|dr)
nickname? string
}
Employee {
person#Person {
title? // Make required field optional
nickname // Make optional field required
}
}// INVALID - Circular reference
TypeA {
field#TypeB
}
TypeB {
field#TypeA // Error: Circular reference detected
}// INVALID - Self-reference
TreeNode {
value string
children#TreeNode[] // Error: Self-reference not allowed
}Person {
name string
age number
bio? string
}
User {
profile#Person {
age? // Make required field optional
bio // Make optional field required
}
}name remains requiredage becomes optional (was required)bio becomes required (was optional)Person {
title string(mr|mrs|miss|dr|prof)
status string(active|inactive|pending)
}
Customer {
contact#Person {
title string(mr|mrs|miss) // Subset of original enum
}
}Person {
name string<minLength:1>
age number<min:18|max:120>
}
// ALL OF THESE ARE INVALID:
User {
profile#Person {
age number<min:25> // ❌ Cannot change modifier values
age number<min:18|max:65> // ❌ Cannot add new modifiers
name string // ❌ Cannot remove modifiers
}
}Person {
name string
age number
}
// INVALID:
User {
profile#Person {
email string // ❌ Cannot add new fields
}
}Person {
age number
name string
}
// INVALID:
User {
profile#Person {
age string // ❌ Cannot change type from number to string
}
}Address {
street string
city string
}
Person {
name string
homeAddress#Address
}
// INVALID:
User {
contact#Person {
workAddress#Address // ❌ Cannot add references in modification block
}
}Person {
firstName string<minLength:1>
lastName string<minLength:1>
age number<min:18|max:120>
title string(mr|mrs|miss|dr|prof)
nickname? string
bio? string<maxLength:500>
}
// Using Person as-is
Employee {
contact#Person
}
// Modifying optionality and narrowing enums
Customer {
primaryContact#Person {
age? // Make required optional
nickname // Make optional required
title string(mr|mrs|miss) // Narrow enum to subset
}
}Person {
name string<minLength:1>
age number<min:18>
title string(mr|mrs|miss)
}
// ALL INVALID:
User {
profile#Person {
email string // ❌ Cannot add fields
age number<min:25> // ❌ Cannot modify constraints
name string // ❌ Cannot remove modifiers
title string(mr|mrs|miss|dr) // ❌ Cannot extend enum
age string // ❌ Cannot change type
}
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
User {
id string<ulid>
email string<isEmail>
timestamps#Timestamps
}
Article {
id string<ulid>
title string
content string
timestamps#Timestamps
}Address {
street string
city string
zipCode string
}
ContactInfo {
email string<isEmail>
phone string
address#Address
}
User {
name string
contact#ContactInfo
}User has name, and contact contains email, phone, and address (which itself contains street, city, zipCode).Organization {
Departments {
Engineering {
name string
headCount number
}
Sales {
name string
territory string
}
}
}
Team {
engineering#Organization#Departments#Engineering
sales#Organization#Departments#Sales
}Address {
street string
city string
}
User {
name string
email string
homeAddress#Address // Required
workAddress#Address? // Optional
billingAddress#Address? // Optional
}Tag {
name string<lowercase>
color string
}
Article {
title string
content string
tags#Tag[] // Array of Tag references
relatedTags#Tag[]? // Optional array of Tags
}BaseUser {
firstName string
lastName string
email string<isEmail>
age number<min:13>
role string(user|admin|moderator)
bio? string
}
// Use as-is
StandardUser {
user#BaseUser
}
// Make some fields optional
GuestUser {
user#BaseUser {
email? // Email becomes optional for guests
age? // Age becomes optional
}
}
// Narrow enums
AdminUser {
user#BaseUser {
role string(admin) // Only admin role allowed
}
}
// Combine modifications
RestrictedUser {
user#BaseUser {
age? // Make optional
bio // Make required
role string(user|moderator) // Narrow enum
}
}# in NamesConfig {
"api#key" string // # is literal, not a reference
"user#id" string // # is literal
"field#123" number // # is literal
apiToken#AuthToken // This IS a reference to AuthToken
}# for references - Links fields to other type definitions#Type1#Type2 for nested type access"field#name" to treat # as literal character:: to create an alias following the pattern inputName:outputName:fname:firstName string
lname:lastName string
email:emailAddress stringfname)firstName)User {
id string
fname:firstName string
lname:lastName string
email:emailAddress string<isEmail>
}{
"id": "abc123",
"fname": "John",
"lname": "Doe",
"email": "john@example.com"
}{
"id": "abc123",
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john@example.com"
}baseName#reference:alias[length]?, aliases come in position 3, after references:// Just alias
fname:firstName string
// Reference + alias
contact#Person:primaryContact
// Alias + array
tags:categories[] string
// Reference + alias + array
items#Product:productList[]
// All components
owners#Person:administrators[]?// Valid
fname:firstName string
// Invalid - multiple aliases not allowed
fname:middleName:firstName string // ❌ Error
a:b:c string // ❌ Error# symbol in an alias has no reference meaning - it's treated as a literal character:field:output#name string
// Input: field
// Output: output#name (# is literal, like "output#name")
data:user#id string
// Input: data
// Output: user#id (# is literal)// Spaces in output name
fname:"First Name" string
age:"Age (years)" number
// Special characters in output name
id:"user-id" string
email:"user@email" string
// Both input and output quoted
"in-field":"out-field" string
"user_name":"User Name" stringfield: string // ❌ Missing output name
:output string // ❌ Missing input name
: string // ❌ Missing bothPerson {
name string
age number
}
User {
id string
contact#Person:primaryContact
backup#Person:emergencyContact?
}User.contact or User.backup (using the input name)primaryContact and emergencyContactTag {
name string
color string
}
Article {
items:products[] string
tags#Tag:categories[]
notes:comments[10]? string
}items, tags, notesproducts, categories, commentsCreateUserRequest {
fname:firstName string<trim>
lname:lastName string<trim>
email:emailAddress string<isEmail|lowercase>
pwd:password string<minLength:8>
dob:dateOfBirth string<isISO>
}{
"fname": "Jane",
"lname": "Smith",
"email": "jane@example.com",
"pwd": "secretpass",
"dob": "1990-05-15"
}{
"firstName": "Jane",
"lastName": "Smith",
"emailAddress": "jane@example.com",
"password": "secretpass",
"dateOfBirth": "1990-05-15"
}User {
id string<ulid>
fname:first_name string
lname:last_name string
email:email_address string<isEmail>
created:created_at string<isISO>
updated:updated_at string<isISO>
}Product {
id:product_id string
name:product_name string
desc:product_description string
price:unit_price number<min:0>
qty:quantity_available number<min:0>
}Address {
street string
city string
zipCode string
}
Contact {
name string
email string
}
Company {
name string
addr#Address:primaryAddress
billing#Address:billingAddress
owner#Contact:primaryContact
tech#Contact:technicalContact?
}Config {
key:api#key string
token:auth#token string
endpoint:api.endpoint string
user:user@domain string
}key, token, endpoint, userapi#key, auth#token, api.endpoint, user@domainUserProfile {
fname:"First Name" string
lname:"Last Name" string
age:"Age (years)" number
addr:"Home Address" string
"phone-number":"Phone Number" string
}Organization {
// Simple alias
name:organizationName string
// Alias with reference
owner#Person:primaryOwner
// Alias with array
tags:categories[] string
// Alias with reference and array
employees#Person:staff[]
// Alias with reference, array, and optional
advisors#Person:boardAdvisors[]?
// All components
contacts#Contact:emergencyContacts[3]?
}Address {
street string
city string
zipCode string
}
OrderItem {
productId string
quantity number
price number
}
Order {
id:orderId string<ulid>
custId:customerId string<ulid>
items#OrderItem:orderItems[]
subtotal:subTotalAmount number<min:0>
tax:taxAmount number<min:0>
total:totalAmount number<min:0>
ship#Address:shippingAddress
bill#Address:billingAddress
created:createdAt string<isISO>
status:orderStatus string(pending|confirmed|shipped|delivered)
}inputName:outputNamea:b:c)#reference:alias[]# in aliases has no reference meaning[][] to declare an array:tags[] string
scores[] number
flags[] boolean
items[] objectbaseName#reference:alias[length]?[] indicate an array of any length (one or more elements):User {
tags[] string
scores[] number
emails[] string<isEmail>
}{
"tags": ["javascript", "nodejs"],
"scores": [85, 92, 78],
"emails": ["user@example.com"]
}{
"tags": [], // ❌ Empty array not allowed
"scores": [] // ❌ Empty array not allowed
}[n] to require an exact number of elements:User {
topScores[3] number
recentTags[5] string
coordinates[2] number
}{
"topScores": [95, 87, 82],
"recentTags": ["a", "b", "c", "d", "e"],
"coordinates": [40.7128, -74.0060]
}{
"topScores": [95, 87], // ❌ Need exactly 3
"topScores": [95, 87, 82, 90], // ❌ Need exactly 3
"coordinates": [40.7128] // ❌ Need exactly 2
}[0], [1], [100] are syntactically valid[-1] is an error[5.5] is an error[0] means exactly zero elements (effectively an empty array requirement)Product {
tags[] string
prices[] number
features[] boolean
metadata[] object
}User {
data[] // Can be [1, "string", true, {}, []]
mixed[] // Can contain any types
}{
"data": [1, "hello", true, {"key": "value"}, [1, 2, 3]],
"mixed": ["text", 42, false]
}Tag {
name string
color string
}
Address {
street string
city string
}
User {
tags#Tag[]
addresses#Address[]
contacts#Contact[3]
}tags#Tag[] not tags[]#TagUser {
tags:categories[] string
items:products[] object
notes:comments[5] string
}? at the end:User {
tags[]? string // Optional variable-length array
scores[10]? number // Optional fixed-length array
addresses#Address[]? // Optional array of references
}{
// Field omitted - valid because optional
}
{
"tags": ["javascript"] // Valid - has elements
}{
"tags": [] // ❌ Still invalid - empty array not allowed even when optional
}Company {
name string
departments[] {
name string
employees[] {
id string
name string
skills[] string
}
}
}tags[] string
tags [ ] string
tags [5] string
tags [ 5 ] stringBlogPost {
title string
content string
tags[] string
viewCounts[] number
isPublished[] boolean
}GeoLocation {
coordinates[2] number // [latitude, longitude]
bounds[4] number // [north, south, east, west]
}
ColorPalette {
primaryColors[3] string // Exactly 3 colors
rgbValues[3] number // [red, green, blue]
}Order {
items[] {
productId string
quantity number
price number
}
shippingSteps[3] {
location string
timestamp string<isISO>
status string
}
}Contact {
name string
email string
phone string
}
Product {
id string
name string
price number
}
Order {
id string
customerId string
items#Product[]
contacts#Contact[2]
notes[]? string
}User {
items:products[] string
tags:categories[] string
notes:comments[5] string
contacts#Person:emergencyContacts[3]
}items, tags, notes, contactsproducts, categories, comments, emergencyContactsUser {
email string
tags[]? string // Optional
addresses#Address[]? // Optional
backupEmails[3]? string // Optional, but if present must have exactly 3
}Config {
settings[] // Can be ["string", 42, true, {}]
mixedData[] // Any combination of types
values[] // No type restriction
}{
"settings": ["debug", true, 8080, {"timeout": 3000}],
"mixedData": [1, "two", false],
"values": ["a", 1, {"key": "val"}]
}Image {
url string<isUrl>
alt string
}
Review {
rating number<min:1|max:5>
comment string
author string
}
Product {
id string<ulid>
name string
description string
price number<min:0>
images#Image[]
reviews#Review[]?
tags[] string
relatedProductIds[] string
dimensions[3] number // [length, width, height]
colors[] string
sizes[] string(XS|S|M|L|XL|XXL)
}User {
id string
username string
}
HashTag {
text string
count number
}
Comment {
id string
text string
authorId string
timestamp string<isISO>
}
Post {
id string<ulid>
content string
authorId string
mentions#User[]?
hashtags#HashTag[]
comments#Comment[]?
mediaUrls[]? string<isUrl>
likes[] string // Array of user IDs
coordinates[2]? number // [lat, long] - optional
}DataPoint {
timestamp string<isISO>
value number
}
Metric {
name string
unit string
dataPoints#DataPoint[]
summary[5] number // [min, max, avg, median, stddev]
percentiles[4] number // [25th, 50th, 75th, 95th]
}
Dashboard {
title string
metrics#Metric[]
tags[] string
refreshInterval number
}// Not supported
matrix[][] number
data[][] string
nested[][] object// Empty arrays not allowed
tags[] string
// But you can make the field optional
tags[]? string
// Then handle these cases:
// Field omitted entirely ✓
// Field present with elements ✓
// Field present but empty ❌[] for one or more elements[n] for exactly n elements#ref:alias[]? after brackets: tags[]??? allows you to make fields optional, meaning they can be omitted from the data.? at the end of the declaration to mark a field as optional:User {
email string // Required
phone? string // Optional
bio? string // Optional
}? always comes last: baseName#reference:alias[length]??):User {
name string // Must be present
age number // Must be present
bio? string // Can be omitted
website? string<isUrl> // Can be omitted
}{
"name": "John Doe",
"age": 30
// bio and website omitted - valid because optional
}
{
"name": "Jane Smith",
"age": 25,
"bio": "Software engineer"
// website omitted - still valid
}{
"name": "John Doe"
// age missing - invalid because required
}null.User {
bio? string
}{
// bio omitted - valid
}
{
"bio": undefined // Valid
}{
"bio": null // Depends on implementation/modifiers
}isNonNull - Field cannot be nullisNull - Field must be nullUser {
bio? string<isNonNull> // Optional, but if present cannot be null
deletedAt? string<isNull> // Optional, and must be null if present
}User {
email? string<isEmail|lowercase>
age? number<min:18|max:120>
website? string<isUrl>
}{
// All fields omitted - valid
}
{
"email": "user@example.com", // Valid email format
"age": 25 // Within min/max range
}{
"email": "invalid-email" // ❌ Fails validation even though optional
}
{
"age": 15 // ❌ Below minimum even though optional
}User {
// Optional primitives
bio? string
age? number
isActive? boolean
// Optional objects
metadata? object
// Optional arrays
tags[]? string
scores[10]? number
// Optional references
address#Address?
profile#UserProfile?
}Address {
street string // Required in Address
city string // Required in Address
zipCode string // Required in Address
}
User {
name string
homeAddress#Address? // Address is optional
workAddress#Address // Address is required
}homeAddress can be omitted entirelyhomeAddress is provided, it must have street, city, and zipCode (all required within Address)workAddress must be provided and must have all required Address fields{
"name": "John Doe",
"workAddress": {
"street": "123 Main St",
"city": "New York",
"zipCode": "10001"
}
// homeAddress omitted - valid because optional
}{
"name": "John Doe",
"homeAddress": {
"street": "123 Main St"
// Missing city and zipCode - invalid even though homeAddress is optional
},
"workAddress": {
"street": "456 Oak Ave",
"city": "Boston",
"zipCode": "02101"
}
}User {
tags[]? string // Optional array
scores[10]? number // Optional fixed-length array
addresses#Address[]? // Optional array of references
}{
// All arrays omitted - valid
}
{
"tags": ["javascript", "nodejs"] // Present with elements - valid
}{
"tags": [] // ❌ Empty array still invalid even when optional
}
{
"scores": [85, 92] // ❌ Must have exactly 10 elements if present
}User {
fname:firstName string
lname:lastName string
mname:middleName? string
nick:nickname? string
}fname, lname, mname, nickfirstName, lastName, middleName, nicknamemname and nick can be omitted? marker works at any nesting level:Company {
name string
address? {
street string
city string
zip? string // zip is optional within address
}
employees[] {
name string
title? string // title is optional within each employee
department? {
name string
manager? string // manager is optional within department
}
}
}User {
id string<ulid>
email string<isEmail>
username string<minLength:3>
// Optional profile fields
firstName? string
lastName? string
bio? string<maxLength:500>
avatarUrl? string<isUrl>
website? string<isUrl>
phone? string
// Required timestamps
createdAt string<isISO>
updatedAt string<isISO>
}Product {
id string<ulid>
name string
description string
price number<min:0>
// Optional fields
compareAtPrice? number<min:0>
costPrice? number<min:0>
sku? string
barcode? string
// Optional arrays
tags[]? string
images[]? string<isUrl>
// Required status
isActive boolean
}CreateUserRequest {
email string<isEmail>
password string<minLength:8>
username string<minLength:3>
// Optional during creation
firstName? string
lastName? string
phoneNumber? string
}
CreateUserResponse {
id string<ulid>
email string
username string
// Optional in response
firstName? string
lastName? string
phoneNumber? string
createdAt string<isISO>
}ContactInfo {
email string<isEmail>
phone? string
address? {
street string
city string
state string
zipCode? string
}
}
User {
name string
contactInfo? #ContactInfo // Entire contact info is optional
emergencyContact? {
name string
relationship string
phone string
}
}Address {
street string
city string
state string
zipCode string
}
PaymentMethod {
type string
last4 string
}
Order {
id string<ulid>
customerId string
total number
// Optional references
shippingAddress#Address?
billingAddress#Address?
paymentMethod#PaymentMethod?
// Optional primitive
notes? string
giftMessage? string
// Required fields
status string
createdAt string<isISO>
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
Author {
id string
name string
email string
}
Category {
id string
name string
}
Article {
id string<ulid>
title string<minLength:1|maxLength:200>
content string
// Required reference
author#Author
timestamps#Timestamps
// Optional reference
primaryCategory#Category?
// Optional arrays
tags[]? string
categories#Category[]?
relatedArticles[]? string
// Optional fields with validation
excerpt? string<maxLength:300>
featuredImageUrl? string<isUrl>
publishedAt? string<isISO>
// Required status
status string(draft|published|archived)
}Address {
street string
city string
state string
zipCode string
country string
}
Customer {
id string
email string
firstName string
lastName string
}
OrderItem {
productId string
productName string
quantity number<min:1>
unitPrice number<min:0>
subtotal number<min:0>
}
Order {
id string<ulid>
orderNumber string<unique>
// Required references
customer#Customer
items#OrderItem[]
shippingAddress#Address
// Optional reference
billingAddress#Address? // Use shipping if not provided
// Required amounts
subtotal number<min:0>
tax number<min:0>
shipping number<min:0>
total number<min:0>
// Optional fields
discountCode? string
discountAmount? number<min:0>
giftWrap? boolean
giftMessage? string<maxLength:500>
specialInstructions? string
// Optional tracking
trackingNumber? string
carrier? string
estimatedDelivery? string<isISO>
// Required status
status string(pending|processing|shipped|delivered|cancelled)
createdAt string<isISO>
}User {
email string
phoneNumber? string // Not everyone provides phone
}Config {
host string
port? number // Default to 3000 if not provided
}Profile {
username string
bio? string // Can be added later
avatarUrl? string // Can be added later
}Product {
price number
salePrice? number // Only if on sale
}User {
id string
email string // Always needed for user
}Order {
customerId string
total number // Must always be calculated
}Article {
title string
content string // Can't publish without content
}? to make optional? always comes last: baseName#reference:alias[length]?> and ...> or ... to copy fields from another object:BaseUser {
id string
email string
createdAt string
}
User {
>BaseUser // Copies id, email, createdAt
name string // Adds name
}User contains id, email, createdAt, and name.User {
>BaseUser // Shorthand
name string
}
User {
...BaseUser // Longhand
name string
}User {
>BaseUser
...Timestamps
name string
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
AuditFields {
createdBy string<ulid>
updatedBy string<ulid>
}
Article {
>Timestamps // Adds createdAt, updatedAt
>AuditFields // Adds createdBy, updatedBy
title string
content string
}Article has 6 fields: createdAt, updatedAt, createdBy, updatedBy, title, content.GlobalConfig {
apiVersion string
timeout number
}
>GlobalConfig // Copies fields into root scope
User {
name string
email string
}Organization {
Settings {
theme string
language string
}
}
App {
>config#Organization#Settings // Copy from nested type
appName string
}>someObject#Type1#Type2 // Copy from Type2 within Type1>#SomeType // ❌ Error: Cannot copy from reference without base object>"some Object"
>'another-object'
>"config#settings"BaseUser {
name string
age number
email string
}
User {
>BaseUser
name number // Overrides name from BaseUser (was string, now number)
bio string // Adds new field
}User has name as number (overridden), age as number (from BaseUser), email as string (from BaseUser), and bio as string (new).Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
AuditFields {
createdBy string<ulid>
updatedBy string<ulid>
}
SoftDelete {
deletedAt? string<isISO>
isDeleted boolean
}
Article {
>Timestamps
>AuditFields
>SoftDelete
title string
content string
}Object1 {
name string
}
Object2 {
name number
}
User {
>Object1 // name is string
>Object2 // name becomes number (later copy wins)
}BaseObject {
id string
createdAt string
}
ExtendedObject {
>BaseObject // Has id, createdAt
name string
}
FinalObject {
>ExtendedObject // Gets id, createdAt (from BaseObject) AND name (from ExtendedObject)
description string
}FinalObject has id, createdAt, name, and description.select or exclude modifiers.<select:fieldNames>:FullUser {
id string
email string
passwordHash string
internalNotes string
firstName string
lastName string
}
PublicUser {
>FullUser<select:id,email,firstName,lastName>
}PublicUser has only id, email, firstName, and lastName.<exclude:fieldNames>:FullUser {
id string
email string
passwordHash string
internalNotes string
firstName string
lastName string
}
SafeUser {
>FullUser<exclude:passwordHash,internalNotes>
}SafeUser has id, email, firstName, and lastName.select and exclude modifiers can contain:_.#>someObject<select:field_name> // Underscore OK
>someObject<select:field.name> // Period OK
>someObject<select:field#ref> // Hash OK
>someObject<select:"field-name"> // Hyphen requires quotes
>someObject<select:"field,name"> // Comma requires quotes
>someObject<select:field1,"field,2",field3> // MixedUser {
name string
email string
}
Profile {
>User<select:name,email,bio> // bio doesn't exist - ignored
>User<exclude:phone> // phone doesn't exist - ignored
}Organization {
Settings {
theme string
language string
apiKey string
secretToken string
}
}
PublicConfig {
>config#Organization#Settings<exclude:apiKey,secretToken>
}! to negate modifiers:>someObject<!exclude:age> // !exclude = select
>someObject<!select:age> // !select = exclude>User<select:name,email>
>User<!exclude:name,email> // Same as above
>User<exclude:password>
>User<!select:password> // Same as aboveselect and exclude in the same copy operation:// Invalid
>someObject<select:a|exclude:b> // ❌ Error
>someObject<select:a,b|exclude:c> // ❌ Error// Valid
>someObject<select:a,b,c>
>someObject<exclude:x,y,z>>someObject:alias // ❌ Error: Copy cannot have alias>someObject[] // ❌ Error: Copy cannot be array
>someObject? // ❌ Error: Copy cannot be optional>someObject string // ❌ Error: Copy cannot have expression
>someObject { // ❌ Error: Copy cannot have block
field string
}select and exclude modifiers are allowed, and they cannot be grouped:>someObject<(select:age)> // ❌ Error: Cannot group
>someObject<select:a|required> // ❌ Error: Only select/exclude allowed
>someObject<select:a|minLength:5> // ❌ Error: Only select/exclude allowedUser {
>User // ❌ Error: Self-reference not allowed
}ObjectA {
>ObjectB
}
ObjectB {
>ObjectA // ❌ Error: Circular dependency
}EmptyObject {
}
User {
>EmptyObject // Valid but adds no fields
name string
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
User {
>Timestamps
id string<ulid>
email string<isEmail>
name string
}
Article {
>Timestamps
id string<ulid>
title string
content string
}Identifiable {
id string<ulid>
}
Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
Auditable {
createdBy string<ulid>
updatedBy string<ulid>
}
SoftDeletable {
deletedAt? string<isISO>
isDeleted boolean
}
Post {
>Identifiable
>Timestamps
>Auditable
>SoftDeletable
title string
content string
authorId string<ulid>
}InternalUser {
id string<ulid>
email string<isEmail>
passwordHash string
saltValue string
failedLoginAttempts number
lastLoginIp string
internalNotes string
firstName string
lastName string
role string
isActive boolean
}
// For public API responses
PublicUser {
>InternalUser<select:id,email,firstName,lastName,isActive>
}
// For admin dashboard
AdminUser {
>InternalUser<exclude:passwordHash,saltValue>
}
// For authentication context
AuthUser {
>InternalUser<select:id,email,role,isActive>
}DefaultConfig {
host string
port number
timeout number
retries number
logLevel string
}
ProductionConfig {
>DefaultConfig
host string // Override host
enableMetrics boolean // Add new field
}
DevelopmentConfig {
>DefaultConfig
logLevel string // Override to different level
debugMode boolean // Add new field
}ApiMetadata {
apiVersion string
requestId string<ulid>
timestamp string<isISO>
}
SuccessResponse {
>ApiMetadata
success boolean
data object
}
ErrorResponse {
>ApiMetadata
success boolean
error string
message string
details? object
}BaseEntity {
id string<ulid>
createdAt string<isISO>
}
NamedEntity {
>BaseEntity
name string
}
TaggedEntity {
>NamedEntity // Gets id, createdAt, name
tags[] string
}
Product {
>TaggedEntity // Gets id, createdAt, name, tags
price number
description string
}Identifiable {
id string<ulid>
}
Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
Auditable {
createdBy string<ulid>
updatedBy string<ulid>
}
PriceInfo {
price number<min:0>
currency string(USD|EUR|GBP)
taxRate? number<min:0|max:1>
}
Product {
>Identifiable
>Timestamps
>Auditable
>PriceInfo
name string
description string
sku string<unique>
inStock boolean
stockQuantity number<min:0>
}
Order {
>Identifiable
>Timestamps
>Auditable
orderNumber string<unique>
customerId string<ulid>
total number<min:0>
status string(pending|confirmed|shipped|delivered)
}TenantAware {
tenantId string<ulid>
}
SoftDelete {
deletedAt? string<isISO>
deletedBy? string<ulid>
}
Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
User {
>TenantAware
>Timestamps
>SoftDelete
id string<ulid>
email string<isEmail>
name string
role string
}
Document {
>TenantAware
>Timestamps
>SoftDelete
id string<ulid>
title string
content string
ownerId string<ulid>
}BaseProduct {
name string
price number
description string
isActive boolean
}
DigitalProduct {
>BaseProduct
price number<min:0|max:1000> // Override with stricter constraint
downloadUrl string<isUrl> // Add new field
}
PhysicalProduct {
>BaseProduct
weight number<min:0> // Add new field
dimensions object // Add new field
shippingClass string // Add new field
}copyOverrideBehaviour configuration option in .SPECML.config.json controls how conflicts are handled:{
"copyOverrideBehaviour": "override"
}"error" (default) - Throw error when copied field conflicts with local declaration"override" - Local declarations override copied fieldsBaseObject {
name string
}
User {
>BaseObject
name number // ❌ Error: field already declared
}BaseObject {
name string
}
User {
>BaseObject
name number // ✓ Valid: local declaration wins, name becomes number
}> and ... are completely interchangeable<select:> or <exclude:> to filter fields>, but for entire files instead of individual types.// users.spec
User {
name string
email string
}
// main.spec
import users.spec
// Now User is available here
Profile {
user#User
}import followed by the file path:import data.spec
import users.spec
import ../shared/common.specimport users.spec
import products.spec./users.spec./products.spec../:import ../common.spec
import ../../shared/base.spec../common.spec../../shared/base.spec@@/ to reference the base directory:import @/schemas/users.spec
import @/shared/common.specbaseDirectory is configured in .SPECML.config.json, use that path{
"baseDirectory": "/path/to/my/specs"
}import "user data.spec"
import "schemas/my-file.spec"
import "../shared files/common.spec"User {
name string
email string
}Product {
title string
price number
}import users.spec
import products.spec
Order {
customer#User
items#Product[]
}User {
name string
email string
}
Product {
title string
price number
}
Order {
customer#User
items#Product[]
}import file2.spec
import file1.spec
// file2.spec content comes first
// file1.spec content comes second// base.spec
Timestamps {
createdAt string
updatedAt string
}
// main.spec
import base.spec
User {
>Timestamps // Works because Timestamps is already imported
name string
}User {
name string
email string
}
import products.spec // Import in the middle
Order {
customer#User
items#Product[]
}
import payments.spec // Import at the endimport file2.spec
Type1 {
field string
}import file1.spec
Type2 {
field string
}file1.spec starts importing file2.specfile2.spec, it encounters import file1.spec.SPECML.config.json:{
"circularImportBehavior": "ignore"
}"ignore" (default) - Silently ignore circular imports"warn" - Log a warning but continue"error" - Throw an error on circular importsTimestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
SoftDelete {
deletedAt? string<isISO>
isDeleted boolean
}
Address {
street string
city string
state string
zipCode string
country string<length:2|uppercase>
}import shared/base.spec
User {
>Timestamps
>SoftDelete
id string<ulid>
name string
homeAddress#Address
}import shared/base.spec
Product {
>Timestamps
id string<ulid>
title string
price number
}project/
├── schemas/
│ ├── users/
│ │ ├── user.spec
│ │ ├── profile.spec
│ │ └── preferences.spec
│ ├── orders/
│ │ ├── order.spec
│ │ ├── order-item.spec
│ │ └── payment.spec
│ ├── shared/
│ │ ├── base.spec
│ │ └── common.spec
│ └── main.specimport shared/base.spec
import shared/common.spec
import users/user.spec
import users/profile.spec
import orders/order.spec
import orders/payment.spec{
"baseDirectory": "./schemas"
}@/ for all imports:import @/shared/base.spec
import @/users/user.spec
import @/orders/order.specErrorResponse {
error string
message string
timestamp string<isISO>
}
ValidationErrorResponse {
error string
message string
details[] {
field string
message string
}
}AuthHeaders {
Authorization string
X-API-Key string
}
StandardHeaders {
Content-Type string(application/json)
X-Request-ID string<ulid>
}import shared/responses.spec
import shared/headers.spec
CreateUserEndpoint {
method POST
path /api/users
headers {
>StandardHeaders
>AuthHeaders
}
body {
email string<isEmail>
password string<minLength:8>
}
response {
success {
status 201
body {
id string
email string
}
}
validation_error {
status 422
body#ValidationErrorResponse
}
server_error {
status 500
body#ErrorResponse
}
}
}fintech-api/
├── schemas/
│ ├── base/
│ │ ├── timestamps.spec
│ │ ├── audit.spec
│ │ └── money.spec
│ ├── users/
│ │ ├── customer.spec
│ │ └── kyc.spec
│ ├── accounts/
│ │ ├── account.spec
│ │ └── transaction.spec
│ ├── api/
│ │ ├── headers.spec
│ │ ├── responses.spec
│ │ └── endpoints.spec
│ └── main.specTimestamps {
createdAt string<isISO>
updatedAt string<isISO>
}AuditMetadata {
createdBy string<ulid>
updatedBy string<ulid>
}Money {
amount number
currency string<uppercase|length:3>(USD|EUR|GBP)
}import ../base/timestamps.spec
import ../base/audit.spec
Customer {
>Timestamps
>AuditMetadata
id string<ulid>
email string<isEmail>
firstName string
lastName string
status string(active|suspended|closed)
}import ../base/timestamps.spec
import ../base/audit.spec
import ../base/money.spec
Account {
>Timestamps
>AuditMetadata
id string<ulid>
accountNumber string<unique>
customerId string<ulid>
balance#Money
status string(active|frozen|closed)
}import ../base/timestamps.spec
import ../base/money.spec
Transaction {
>Timestamps
id string<ulid>
accountId string<ulid>
amount#Money
type string(debit|credit)
description string
balanceAfter#Money
}AuthHeaders {
Authorization string
X-API-Key string
}
StandardHeaders {
Content-Type string(application/json)
X-Request-ID string<ulid>
}import ../base/timestamps.spec
ApiError {
error string
message string
timestamp string<isISO>
requestId? string
}
ValidationError {
>ApiError
details[] {
field string
message string
code string
}
}import headers.spec
import responses.spec
import ../users/customer.spec
import ../accounts/account.spec
import ../accounts/transaction.spec
CreateAccountEndpoint {
method POST
path /api/accounts
headers {
>AuthHeaders
>StandardHeaders
}
body {
customerId string<ulid>
accountType string(checking|savings)
initialDeposit? number<min:0>
currency string(USD|EUR|GBP)
}
response {
success {
status 201
body#Account
}
validation_error {
status 422
body#ValidationError
}
unauthorized {
status 401
body#ApiError
}
}
}
GetTransactionHistoryEndpoint {
method GET
path /api/accounts/:accountId/transactions
headers {
>AuthHeaders
}
params {
accountId string<ulid>
}
query {
startDate? string<isISO>
endDate? string<isISO>
type? string(debit|credit)
page? number<min:1>
limit? number<min:1|max:100>
}
response {
success {
status 200
body {
transactions#Transaction[]
pagination {
page number
limit number
total number
hasMore boolean
}
}
}
not_found {
status 404
body#ApiError
}
}
}import api/endpoints.spec
// All types and endpoints from imported files are now availableschemas/
├── users/
├── products/
├── orders/
└── shared/baseDirectory and use @/ imports for consistency:{
"baseDirectory": "./schemas"
}import @/shared/base.spec
import @/users/user.specimport shared/base.spec // Base types first
import shared/common.spec
import users/user.spec // Domain types after
import products/product.spec// ✅ Good - single concern
// timestamps.spec
Timestamps { ... }
// ✅ Good - single concern
// user.spec
User { ... }
// ❌ Avoid - mixed concerns
// everything.spec
Timestamps { ... }
User { ... }
Product { ... }
Order { ... }// Requires: shared/base.spec for Timestamps
// Requires: shared/money.spec for Money type
import shared/base.spec
import shared/money.spec
Account {
>Timestamps
balance#Money
}> compose schemas, but they work at different levels:| Feature | Import | Copy Operator > |
|---|---|---|
| Scope | File-level | Type-level |
| Usage | import file.spec | >TypeName |
| Brings in | Entire file contents | Specific type's fields |
| Location | Anywhere in file | Inside type definitions |
| Circular handling | Configurable | Not allowed |
// shared.spec
Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
// users.spec
import shared.spec
User {
>Timestamps // Copy operator
name string
}{
"baseDirectory": "./schemas",
"circularImportBehavior": "ignore"
}baseDirectory (string)@/circularImportBehavior (string)"ignore" | "warn" | "error""ignore"./), parent (../), base (@/)baseDirectory for consistencydeclaration expressionage number as "age must be a number"http.method POST as "use POST method"id string<unique> as "create unique index"{ indicates the declaration contains nested fields:Person {
name string
age number
}
Address {
street string
city string
zipCode string
}{ is both the end of the declaration line and the start of a nested block.age number
name string
isActive boolean
metadata object
items arraydata // No expression
config // No expression
metadata // No expression<><> and separate multiple modifiers with |:age number<min:18|max:120>
email string<isEmail|lowercase>
code string<uppercase|length:6>
price number<min:0>field expression<modifierName>field expression<modifierName:value>field expression<modifier1|modifier2|modifier3>
field expression<modifier1:value1|modifier2:value2>name string<minLength:2|maxLength:100>
price number<min:0|max:1000000>
pattern string<matches:"^[A-Z]{3}$">| or :, enclose them in quotes:field string<pattern:"^[a-z]{2,5}$"> // Regex with special chars
field string<custom:"value|with|pipes"> // Pipes in value
url string<startsWith:"http://"|endsWith:.com>
config string<value:"key:value"> // Colon in valueusername string<minLength:3|maxLength:20>
email string<isEmail|lowercase>
code string<uppercase|trim>
slug string<lowercase|trim>age number<min:0|max:150>
price number<min:0>
quantity number<min:1>
percentage number<min:0|max:100>email string<isEmail>
url string<isUrl>
id string<isUUID>
date string<isISO>name string<trim|capitalize>
slug string<lowercase|trim>
code string<uppercase>field string<> // No modifiers applied
field number<> // No modifiers applied!! prefix:url string<!startsWith:"http://"> // Reject http:// URLs
code string<!contains:admin> // Reject if contains "admin"
value number<!min:0> // Negate the min constraint! as logical negation, others may ignore it.()handle string<unique|(lowercase|contains:byte)>
field string<(group1|group2)|other>
value string<(transform1|transform2|validate)><(outer|(inner1|inner2)|outer2)>username string<unique|(lowercase|trim|minLength:3)>
email string<(trim|lowercase)|isEmail>
code string<(uppercase|trim)|(length:6|matches:"^[A-Z]+$")>()| separator:status string(active|inactive|pending)
role string(admin|user|guest)
priority number(1|2|3|4|5)
size string(small|medium|large)field expression(value1|value2|value3)| and can be:status(active|inactive)priority(1|2|3)value(low|1|high|5) (adapter-dependent interpretation)| separator, use quotes:mode string(read|write|"read|write")
separator string(comma|pipe|"pipe|delimited")
format string(json|xml|"json|xml")field string() // No enum constraint
field number() // No enum constraintstatus string<required>(active|inactive)
status (active|inactive)<required>
status string(active|inactive)<required>// Enum with validation
role string<lowercase>(admin|user|moderator)
priority number<min:1|max:5>(1|2|3|4|5)
// Enum with transformation
status string<uppercase|trim>(active|inactive|pending)
code string<trim>(A|B|C|D)
// Complex combinations
email string<isEmail|lowercase>(user@example.com|admin@example.com)
size string<uppercase>(xs|s|m|l|xl)User {
name string
age number
isActive boolean
data object
items array
}User {
email string<isEmail|lowercase>
age number<min:18|max:120>
username string<minLength:3|maxLength:20|trim>
createdAt string<isISO>
}User {
status string(active|inactive|suspended)
role string(admin|user|guest)
theme string(light|dark|auto)
}User {
status string<required|lowercase>(active|inactive|pending)
priority number<min:1|max:3>(1|2|3)
role string<trim>(admin|user|moderator)
}User {
handle string<unique|(lowercase|trim|minLength:3)>
email string<(trim|lowercase)|isEmail>
code string<(uppercase|trim)|length:6>
}Product {
name string<trim|minLength:1|maxLength:200>
status string<lowercase>(active|inactive|archived)
price number<min:0|max:1000000>
sku string<uppercase|unique|length:8>
category string<trim|(lowercase|minLength:2)>(electronics|clothing|food)
}User {
id string<ulid>
email string<isEmail|lowercase|unique>
username string<minLength:3|maxLength:20|trim>
password string<minLength:8>
age number<min:13|max:120>
role string(user|admin|moderator)
status string<lowercase>(active|inactive|suspended)
bio? string<maxLength:500|trim>
website? string<isUrl>
tags[]? string<lowercase|trim>
}GetUserEndpoint {
method GET
path /api/users/:id
params {
id string<ulid>
}
response {
status number(200|404|500)
body object
}
}ServerConfig {
host string<trim>
port number<min:1|max:65535>
environment string(development|staging|production)
logLevel string<uppercase>(DEBUG|INFO|WARN|ERROR)
enableMetrics boolean
timeout number<min:1000|max:30000>
retries number<min:0|max:5>
}Product {
id string<ulid|unique|indexed>
name string<minLength:1|maxLength:200|indexed>
sku string<uppercase|unique|indexed>
price number<min:0>
quantity number<min:0>
status string<indexed>(active|inactive|discontinued)
categoryId string<ulid|indexed>
createdAt string<isISO|indexed>
}UserInput {
firstName string<trim|capitalize>
lastName string<trim|capitalize>
email string<trim|lowercase>
phone string<trim>
zipCode string<trim|uppercase>
country string<uppercase|length:2>
}Order {
id string<ulid>
orderNumber string<unique|uppercase>
items[] {
productId string<ulid>
quantity number<min:1|max:100>
price number<min:0>
}
status string<lowercase>(pending|confirmed|processing|shipped|delivered|cancelled)
paymentStatus string<lowercase>(pending|authorized|captured|failed|refunded)
total number<min:0>
currency string<uppercase|length:3>(USD|EUR|GBP|JPY)
email string<isEmail|lowercase>
phone? string<trim>
notes? string<maxLength:1000|trim>
}SocialProfile {
username string<unique|(lowercase|trim|minLength:3|maxLength:20)>
displayName string<(trim|capitalize)|minLength:1|maxLength:50>
bio? string<(trim|stripHtml)|maxLength:500>
handle string<unique|(lowercase|trim)|(startsWith:@|minLength:4)>
tags[] string<(lowercase|trim)>
links[]? string<isUrl|(trim|lowercase)>
}field string<> // Empty modifiers (no constraints)
field string() // Empty enum (no restriction)
field // No expression (any type)// Compact
age number<min:10|max:100>
status string(active|inactive)
// Spaced
age number < min:10 | max:100 >
status string ( active | inactive )
// Very spaced
age number < min:10 | max:100 >
status string ( active | inactive )<> - Add constraints, transformations, or metadata() - Define allowed values| and : in arguments() - Create modifier pipelines! - Invert modifier logicTimestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
User {
>Timestamps
id string<ulid>
email string<isEmail|lowercase>
name string
}deletedAt, lastModifiedBy, etc.){
"createdAt": "2024-03-15T10:30:00Z",
"updatedAt": "2024-03-15T10:30:00Z",
"id": "01HQXYZ123ABC",
"email": "user@example.com",
"name": "John Doe"
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
SoftDelete {
deletedAt? string<isISO>
deletedBy? string<ulid>
isDeleted boolean
}
Account {
>Timestamps
>SoftDelete
id string<ulid>
accountNumber string<unique>
balance number
}isDeleted flag for quick filtering{
"createdAt": "2024-01-10T08:00:00Z",
"updatedAt": "2024-03-15T14:22:00Z",
"isDeleted": false,
"id": "01ACCOUNT123",
"accountNumber": "ACC-2024-001",
"balance": 15000.50
}{
"createdAt": "2024-01-10T08:00:00Z",
"updatedAt": "2024-03-15T14:22:00Z",
"deletedAt": "2024-03-15T14:22:00Z",
"deletedBy": "01USER789",
"isDeleted": true,
"id": "01ACCOUNT123",
"accountNumber": "ACC-2024-001",
"balance": 0
}Address {
streetLine1 string<trim|minLength:1|maxLength:100>
streetLine2? string<trim|maxLength:100>
city string<trim|minLength:2|maxLength:50>
state string<trim|length:2|uppercase>
postalCode string<trim>
country string<length:2|uppercase>
}
Customer {
id string<ulid>
name string
billingAddress#Address
shippingAddress#Address?
mailingAddress#Address?
}{
"id": "01CUST456",
"name": "Jane Smith",
"billingAddress": {
"streetLine1": "123 Main Street",
"streetLine2": "Apt 4B",
"city": "New York",
"state": "NY",
"postalCode": "10001",
"country": "US"
},
"shippingAddress": {
"streetLine1": "456 Work Plaza",
"city": "Boston",
"state": "MA",
"postalCode": "02101",
"country": "US"
}
}Money {
amount number<min:0>
currency string<uppercase|length:3>(USD|EUR|GBP|JPY|CHF|CAD|AUD)
}
Transaction {
id string<ulid>
accountId string<ulid>
transactionAmount#Money
balanceAfter#Money
timestamp string<isISO>
type string(debit|credit)
status string(pending|completed|failed)
}{
"id": "01TXN789",
"accountId": "01ACC123",
"transactionAmount": {
"amount": 250.50,
"currency": "USD"
},
"balanceAfter": {
"amount": 15250.50,
"currency": "USD"
},
"timestamp": "2024-03-15T14:30:22Z",
"type": "credit",
"status": "completed"
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
KYCDocument {
>Timestamps
id string<ulid>
customerId string<ulid>
documentType string(passport|drivers_license|national_id|utility_bill|bank_statement)
documentNumber string<trim>
documentUrl string<isUrl>
expirationDate? string<isISO>
verificationStatus string(pending|verified|rejected|expired)
verifiedAt? string<isISO>
verifiedBy? string<ulid>
rejectionReason? string
}{
"createdAt": "2024-03-10T09:00:00Z",
"updatedAt": "2024-03-11T15:30:00Z",
"id": "01DOC456",
"customerId": "01CUST789",
"documentType": "passport",
"documentNumber": "P1234567",
"documentUrl": "https://secure-storage.example.com/docs/01DOC456",
"expirationDate": "2030-05-20T00:00:00Z",
"verificationStatus": "verified",
"verifiedAt": "2024-03-11T15:30:00Z",
"verifiedBy": "01ADMIN123"
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
BaseAccount {
>Timestamps
id string<ulid>
accountNumber string<unique>
customerId string<ulid>
balance number
currency string<uppercase|length:3>(USD|EUR|GBP)
status string(active|suspended|closed)
// All tiers have these limits
dailyTransactionLimit number<min:0>
monthlyTransactionLimit number<min:0>
}
BasicAccount {
>BaseAccount
accountType string(basic)
interestRate number<min:0|max:2>
monthlyFee number<min:0>
}
PremiumAccount {
>BaseAccount
accountType string(premium)
interestRate number<min:0|max:5>
cashbackRate number<min:0|max:3>
personalBankerId? string<ulid>
conciergeAccess boolean
}
BusinessAccount {
>BaseAccount
accountType string(business)
businessName string
taxId string
multiUserAccess boolean
apiAccessEnabled boolean
accountManagers[] string<ulid>
}{
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-03-15T14:00:00Z",
"id": "01ACC789",
"accountNumber": "PREM-2024-001",
"customerId": "01CUST456",
"balance": 50000,
"currency": "USD",
"status": "active",
"dailyTransactionLimit": 50000,
"monthlyTransactionLimit": 500000,
"accountType": "premium",
"interestRate": 3.5,
"cashbackRate": 2.0,
"personalBankerId": "01BANKER123",
"conciergeAccess": true
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
Money {
amount number<min:0>
currency string<uppercase|length:3>(USD|EUR|GBP)
}
LedgerEntry {
accountId string<ulid>
entryType string(debit|credit)
amount#Money
}
Transaction {
>Timestamps
id string<ulid>
transactionType string(transfer|payment|deposit|withdrawal|fee)
description string
entries#LedgerEntry[2]
referenceNumber string<unique>
status string(pending|completed|failed|reversed)
metadata? object
}{
"createdAt": "2024-03-15T14:30:00Z",
"updatedAt": "2024-03-15T14:30:00Z",
"id": "01TXN999",
"transactionType": "transfer",
"description": "Transfer to savings account",
"entries": [
{
"accountId": "01CHECKING123",
"entryType": "debit",
"amount": {
"amount": 1000,
"currency": "USD"
}
},
{
"accountId": "01SAVINGS456",
"entryType": "credit",
"amount": {
"amount": 1000,
"currency": "USD"
}
}
],
"referenceNumber": "TXN-2024-03-15-999",
"status": "completed"
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
RiskFactor {
factorName string
factorValue string
weight number<min:0|max:1>
impact string(positive|negative|neutral)
}
RiskAssessment {
>Timestamps
id string<ulid>
customerId string<ulid>
assessmentType string(credit|fraud|aml|kyc)
riskScore number<min:0|max:100>
riskLevel string(low|medium|high|critical)
factors#RiskFactor[]
assessedBy string<ulid>
validUntil string<isISO>
notes? string
}{
"createdAt": "2024-03-15T10:00:00Z",
"updatedAt": "2024-03-15T10:00:00Z",
"id": "01RISK123",
"customerId": "01CUST789",
"assessmentType": "credit",
"riskScore": 72,
"riskLevel": "medium",
"factors": [
{
"factorName": "credit_history",
"factorValue": "good",
"weight": 0.4,
"impact": "positive"
},
{
"factorName": "debt_to_income",
"factorValue": "0.35",
"weight": 0.3,
"impact": "negative"
},
{
"factorName": "payment_history",
"factorValue": "excellent",
"weight": 0.3,
"impact": "positive"
}
],
"assessedBy": "01SYSTEM",
"validUntil": "2024-06-15T10:00:00Z",
"notes": "Automated credit assessment"
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
Address {
streetLine1 string
city string
state string
postalCode string
country string<length:2|uppercase>
}
PaymentMethod {
>Timestamps
id string<ulid>
customerId string<ulid>
type string(credit_card|debit_card|bank_account|digital_wallet)
// Tokenized/masked data - never store raw card numbers
token string<unique>
last4 string<length:4>
// Card-specific fields
brand? string(visa|mastercard|amex|discover)
expiryMonth? number<min:1|max:12>
expiryYear? number<min:2024>
// Bank account fields
bankName? string
accountType? string(checking|savings)
billingAddress#Address
isDefault boolean
isVerified boolean
verifiedAt? string<isISO>
}{
"createdAt": "2024-03-01T08:00:00Z",
"updatedAt": "2024-03-15T10:00:00Z",
"id": "01PAY456",
"customerId": "01CUST789",
"type": "credit_card",
"token": "tok_1234567890abcdef",
"last4": "4242",
"brand": "visa",
"expiryMonth": 12,
"expiryYear": 2027,
"billingAddress": {
"streetLine1": "123 Main St",
"city": "New York",
"state": "NY",
"postalCode": "10001",
"country": "US"
},
"isDefault": true,
"isVerified": true,
"verifiedAt": "2024-03-01T08:30:00Z"
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
Money {
amount number
currency string<uppercase|length:3>(USD|EUR|GBP)
}
Asset {
ticker string<uppercase>
name string
assetType string(stock|bond|etf|mutual_fund|crypto|real_estate)
quantity number<min:0>
costBasis#Money
currentValue#Money
gainLoss#Money
gainLossPercentage number
}
Portfolio {
>Timestamps
id string<ulid>
customerId string<ulid>
portfolioName string
portfolioType string(individual|joint|retirement|trust)
holdings#Asset[]
totalValue#Money
totalCostBasis#Money
totalGainLoss#Money
riskProfile string(conservative|moderate|aggressive)
lastRebalanced? string<isISO>
}{
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-03-15T16:00:00Z",
"id": "01PORT123",
"customerId": "01CUST456",
"portfolioName": "Retirement Portfolio",
"portfolioType": "retirement",
"holdings": [
{
"ticker": "AAPL",
"name": "Apple Inc.",
"assetType": "stock",
"quantity": 100,
"costBasis": {
"amount": 15000,
"currency": "USD"
},
"currentValue": {
"amount": 17500,
"currency": "USD"
},
"gainLoss": {
"amount": 2500,
"currency": "USD"
},
"gainLossPercentage": 16.67
},
{
"ticker": "VTI",
"name": "Vanguard Total Stock Market ETF",
"assetType": "etf",
"quantity": 200,
"costBasis": {
"amount": 40000,
"currency": "USD"
},
"currentValue": {
"amount": 44000,
"currency": "USD"
},
"gainLoss": {
"amount": 4000,
"currency": "USD"
},
"gainLossPercentage": 10.0
}
],
"totalValue": {
"amount": 61500,
"currency": "USD"
},
"totalCostBasis": {
"amount": 55000,
"currency": "USD"
},
"totalGainLoss": {
"amount": 6500,
"currency": "USD"
},
"riskProfile": "moderate",
"lastRebalanced": "2024-01-01T00:00:00Z"
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
Money {
amount number<min:0>
currency string<uppercase|length:3>(USD|EUR|GBP)
}
PaymentSchedule {
dueDate string<isISO>
principalAmount#Money
interestAmount#Money
totalAmount#Money
status string(upcoming|paid|overdue|missed)
paidDate? string<isISO>
}
Loan {
>Timestamps
id string<ulid>
loanNumber string<unique>
customerId string<ulid>
loanType string(personal|auto|mortgage|business|student)
principalAmount#Money
interestRate number<min:0|max:30>
apr number<min:0|max:40>
termMonths number<min:1>
paymentFrequency string(monthly|biweekly|weekly)
currentBalance#Money
paidToDate#Money
remainingPayments number<min:0>
originationDate string<isISO>
firstPaymentDate string<isISO>
maturityDate string<isISO>
paymentSchedule#PaymentSchedule[]
status string(pending|active|paid_off|defaulted|charged_off)
collateralDescription? string
collateralValue? Money
}{
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-03-15T14:00:00Z",
"id": "01LOAN789",
"loanNumber": "LOAN-2024-001",
"customerId": "01CUST456",
"loanType": "auto",
"principalAmount": {
"amount": 25000,
"currency": "USD"
},
"interestRate": 5.5,
"apr": 5.75,
"termMonths": 60,
"paymentFrequency": "monthly",
"currentBalance": {
"amount": 23500,
"currency": "USD"
},
"paidToDate": {
"amount": 1500,
"currency": "USD"
},
"remainingPayments": 57,
"originationDate": "2024-01-15T00:00:00Z",
"firstPaymentDate": "2024-02-15T00:00:00Z",
"maturityDate": "2029-01-15T00:00:00Z",
"paymentSchedule": [
{
"dueDate": "2024-02-15T00:00:00Z",
"principalAmount": {
"amount": 365.52,
"currency": "USD"
},
"interestAmount": {
"amount": 114.58,
"currency": "USD"
},
"totalAmount": {
"amount": 480.10,
"currency": "USD"
},
"status": "paid",
"paidDate": "2024-02-14T08:00:00Z"
}
],
"status": "active",
"collateralDescription": "2023 Toyota Camry",
"collateralValue": {
"amount": 28000,
"currency": "USD"
}
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
AuditMetadata {
submittedBy string<ulid>
submittedAt string<isISO>
reviewedBy? string<ulid>
reviewedAt? string<isISO>
approvedBy? string<ulid>
approvedAt? string<isISO>
}
Transaction {
id string<ulid>
accountId string<ulid>
amount number
currency string
type string
timestamp string<isISO>
description string
counterpartyName? string
counterpartyAccount? string
}
ComplianceReport {
>Timestamps
>AuditMetadata
id string<ulid>
reportType string(sar|ctr|ofac|aml|kyc)
reportNumber string<unique>
customerId string<ulid>
customerName string
// Selectively include transaction data
relatedTransactions#Transaction[]
suspiciousActivity? string
riskLevel string(low|medium|high|critical)
filedWith? string(fincen|sec|occ|fdic)
filingReference? string
filingDate? string<isISO>
status string(draft|under_review|approved|filed|archived)
notes string
attachments[]? string<isUrl>
}{
"createdAt": "2024-03-10T09:00:00Z",
"updatedAt": "2024-03-15T16:00:00Z",
"submittedBy": "01ANALYST123",
"submittedAt": "2024-03-10T09:00:00Z",
"reviewedBy": "01MANAGER456",
"reviewedAt": "2024-03-12T14:00:00Z",
"approvedBy": "01COMPLIANCE789",
"approvedAt": "2024-03-15T16:00:00Z",
"id": "01RPT999",
"reportType": "sar",
"reportNumber": "SAR-2024-0315-001",
"customerId": "01CUST567",
"customerName": "John Doe",
"relatedTransactions": [
{
"id": "01TXN111",
"accountId": "01ACC222",
"amount": 12000,
"currency": "USD",
"type": "wire",
"timestamp": "2024-03-08T10:00:00Z",
"description": "Wire transfer",
"counterpartyName": "ABC Corp",
"counterpartyAccount": "9876543210"
}
],
"suspiciousActivity": "Multiple large wire transfers to high-risk jurisdiction",
"riskLevel": "high",
"filedWith": "fincen",
"filingReference": "FINCEN-SAR-2024-12345",
"filingDate": "2024-03-15T16:30:00Z",
"status": "filed",
"notes": "Customer activity shows structured transactions pattern",
"attachments": [
"https://secure.example.com/reports/sar-2024-0315-001.pdf"
]
}Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
CurrencyBalance {
currency string<uppercase|length:3>
availableBalance number
pendingBalance number
totalBalance number
lastUpdated string<isISO>
}
Wallet {
>Timestamps
id string<ulid>
customerId string<ulid>
walletType string(fiat|crypto|mixed)
balances#CurrencyBalance[]
defaultCurrency string<uppercase|length:3>
status string(active|frozen|closed)
}{
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-03-15T14:00:00Z",
"id": "01WALLET123",
"customerId": "01CUST456",
"walletType": "mixed",
"balances": [
{
"currency": "USD",
"availableBalance": 10000,
"pendingBalance": 500,
"totalBalance": 10500,
"lastUpdated": "2024-03-15T14:00:00Z"
},
{
"currency": "EUR",
"availableBalance": 5000,
"pendingBalance": 0,
"totalBalance": 5000,
"lastUpdated": "2024-03-15T14:00:00Z"
},
{
"currency": "BTC",
"availableBalance": 0.5,
"pendingBalance": 0.1,
"totalBalance": 0.6,
"lastUpdated": "2024-03-15T14:00:00Z"
}
],
"defaultCurrency": "USD",
"status": "active"
}// ===== Shared Base Types =====
Timestamps {
createdAt string<isISO>
updatedAt string<isISO>
}
SoftDelete {
deletedAt? string<isISO>
deletedBy? string<ulid>
isDeleted boolean
}
AuditMetadata {
createdBy string<ulid>
updatedBy string<ulid>
lastAccessedAt? string<isISO>
}
Money {
amount number
currency string<uppercase|length:3>(USD|EUR|GBP|JPY)
}
Address {
streetLine1 string<trim>
streetLine2? string<trim>
city string<trim>
state string<trim|length:2|uppercase>
postalCode string<trim>
country string<length:2|uppercase>
}
// ===== Customer Management =====
KYCDocument {
>Timestamps
id string<ulid>
documentType string(passport|drivers_license|national_id)
documentNumber string<trim>
documentUrl string<isUrl>
expirationDate? string<isISO>
verificationStatus string(pending|verified|rejected)
verifiedAt? string<isISO>
verifiedBy? string<ulid>
}
RiskAssessment {
>Timestamps
id string<ulid>
riskScore number<min:0|max:100>
riskLevel string(low|medium|high)
assessedBy string<ulid>
validUntil string<isISO>
}
Customer {
>Timestamps
>SoftDelete
>AuditMetadata
id string<ulid>
customerNumber string<unique>
// Personal info
firstName string<trim>
lastName string<trim>
dateOfBirth string<isISO>
ssn? string<length:11>
// Contact
email string<isEmail|lowercase|unique>
phone string<trim>
address#Address
// KYC & Compliance
kycDocuments#KYCDocument[]
riskAssessment#RiskAssessment?
// Account relationship
accountIds[] string<ulid>
status string(active|suspended|closed)
tier string(basic|premium|business)
}
// ===== Account Management =====
Account {
>Timestamps
>SoftDelete
>AuditMetadata
id string<ulid>
accountNumber string<unique>
customerId string<ulid>
accountType string(checking|savings|investment)
balance#Money
availableBalance#Money
interestRate? number<min:0|max:10>
minimumBalance? Money
status string(active|frozen|closed)
}
// ===== Transaction Management =====
LedgerEntry {
accountId string<ulid>
entryType string(debit|credit)
amount#Money
}
Transaction {
>Timestamps
>AuditMetadata
id string<ulid>
transactionType string(transfer|payment|deposit|withdrawal|fee)
description string
entries#LedgerEntry[2]
referenceNumber string<unique>
status string(pending|completed|failed|reversed)
// Compliance flags
requiresReview? boolean
reviewedBy? string<ulid>
reviewedAt? string<isISO>
}
// ===== Payment Management =====
PaymentMethod {
>Timestamps
id string<ulid>
customerId string<ulid>
type string(credit_card|debit_card|bank_account)
token string<unique>
last4 string<length:4>
brand? string(visa|mastercard|amex|discover)
expiryMonth? number<min:1|max:12>
expiryYear? number<min:2024>
billingAddress#Address
isDefault boolean
isVerified boolean
}
Payment {
>Timestamps
id string<ulid>
paymentNumber string<unique>
customerId string<ulid>
paymentMethodId string<ulid>
amount#Money
fee? Money
totalAmount#Money
status string(pending|processing|completed|failed|refunded)
description string
merchantName? string
merchantCategory? string
processedAt? string<isISO>
failureReason? string
}
// ===== Loan Management =====
PaymentScheduleItem {
dueDate string<isISO>
principalAmount#Money
interestAmount#Money
totalAmount#Money
status string(upcoming|paid|overdue|missed)
paidDate? string<isISO>
}
Loan {
>Timestamps
>AuditMetadata
id string<ulid>
loanNumber string<unique>
customerId string<ulid>
loanType string(personal|auto|mortgage|business)
principalAmount#Money
interestRate number<min:0|max:30>
apr number<min:0|max:40>
termMonths number<min:1>
currentBalance#Money
paidToDate#Money
originationDate string<isISO>
maturityDate string<isISO>
paymentSchedule#PaymentScheduleItem[]
status string(active|paid_off|defaulted)
collateralValue? Money
}
// ===== Investment Management =====
Asset {
ticker string<uppercase>
name string
assetType string(stock|bond|etf|crypto)
quantity number<min:0>
costBasis#Money
currentValue#Money
}
Portfolio {
>Timestamps
id string<ulid>
customerId string<ulid>
portfolioName string
portfolioType string(individual|retirement|trust)
holdings#Asset[]
totalValue#Money
totalCostBasis#Money
riskProfile string(conservative|moderate|aggressive)
}
// ===== Compliance & Reporting =====
ComplianceReport {
>Timestamps
>AuditMetadata
id string<ulid>
reportType string(sar|ctr|aml)
reportNumber string<unique>
customerId string<ulid>
relatedTransactionIds[] string<ulid>
suspiciousActivity? string
riskLevel string(low|medium|high|critical)
status string(draft|under_review|filed)
filedWith? string(fincen|sec)
filingDate? string<isISO>
}