Type Formats extend TypeScript's type system with validation, transformation, and serialization rules. Instead of writing validation schemas separately, you express constraints directly in your types.
Type Formats provide:
Import string formats from @mionjs/type-formats/StringFormats:
import {FormatEmail, FormatEmailStrict, FormatEmailPunycode} from '@mionjs/type-formats/StringFormats';
import {FormatEmail} from '@mionjs/type-formats/StringFormats';
type UserEmail = FormatEmail;
// Valid
('user@example.com'); // ✓
('user+tag@example.com'); // ✓ (allows + for email aliases)
('user(comment)@test.com'); // ✓
// Invalid
('user@name@example.com'); // ✗ (multiple @)
('@example.com'); // ✗ (missing local part)
FormatStringUse FormatString to define custom string constraints:
import {FormatString} from '@mionjs/type-formats/StringFormats';
// Username: 3-20 chars, lowercase, trimmed
type Username = FormatString<{
minLength: 3;
maxLength: 20;
lowercase: true;
trim: true;
}>;
// Slug with pattern validation
const slugRegex = /^[a-z0-9-]+$/;
type Slug = FormatString<{
minLength: 1;
maxLength: 100;
pattern: {
val: typeof slugRegex;
errorMessage: 'Slug can only contain lowercase letters, numbers, and hyphens';
mockSamples: ['my-post', 'hello-world', 'article-123'];
};
}>;
// Name with allowed characters only
type SafeName = FormatString<{
minLength: 1;
maxLength: 50;
allowedChars: {
val: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ';
errorMessage: 'Name can only contain letters and spaces';
};
capitalize: true;
}>;
| Parameter | Type | Description |
|---|---|---|
minLength | number | Minimum string length |
maxLength | number | Maximum string length |
pattern | {val: RegExp; errorMessage: string; mockSamples: string[]} | Regex validation |
allowedChars | {val: string; errorMessage: string} | Allowed characters |
disallowedChars | {val: string; errorMessage: string} | Disallowed characters |
allowedValues | {val: string[]; errorMessage: string} | Enum-like validation |
lowercase | boolean | Transform to lowercase |
uppercase | boolean | Transform to uppercase |
capitalize | boolean | Capitalize first letter |
trim | boolean | Trim whitespace |
Import number formats from @mionjs/type-formats/NumberFormats:
import {
FormatNumber,
FormatInteger,
FormatFloat,
FormatPositive,
FormatNegative,
FormatPositiveInt,
FormatNegativeInt,
FormatInt8,
FormatInt16,
FormatInt32,
FormatUInt8,
FormatUInt16,
FormatUInt32,
} from '@mionjs/type-formats/NumberFormats';
import {FormatNumber} from '@mionjs/type-formats/NumberFormats';
// Age with valid range
type Age = FormatNumber<{
min: 0;
max: 120;
integer: true;
}>;
// Percentage with decimals
type Percentage = FormatNumber<{
min: 0;
max: 100;
}>;
// Price must be multiple of 0.01 (cents)
type Price = FormatNumber<{
min: 0;
multipleOf: 1; // multipleOf must be integer
integer: true; // store as cents
}>;
FormatNumberimport {FormatNumber} from '@mionjs/type-formats/NumberFormats';
// Age with valid range
type Age = FormatNumber<{
min: 0;
max: 120;
integer: true;
}>;
// Percentage with decimals
type Percentage = FormatNumber<{
min: 0;
max: 100;
}>;
// Price must be multiple of 0.01 (cents)
type Price = FormatNumber<{
min: 0;
multipleOf: 1; // multipleOf must be integer
integer: true; // store as cents
}>;
| Parameter | Type | Description |
|---|---|---|
min | number | Minimum value |
max | number | Maximum value |
integer | boolean | Must be an integer |
float | boolean | Explicitly a float (affects binary serialization) |
multipleOf | number | Must be a multiple of this value |
Here's a comprehensive example demonstrating various type formats:
import {
createIsTypeFn,
createTypeErrorsFn,
createStringifyJsonFn,
createRestoreFromJsonFn,
createMockTypeFn,
} from '@mionjs/run-types';
interface BlogPost {
id: string;
title: string;
content: string;
author: {
name: string;
email: string;
};
tags: string[];
publishedAt: Date;
metadata: Map<string, any>;
}
// Create all needed functions
const isPost = await createIsTypeFn<BlogPost>();
const getPostErrors = await createTypeErrorsFn<BlogPost>();
const stringifyPost = await createStringifyJsonFn<BlogPost>();
const restorePost = await createRestoreFromJsonFn<BlogPost>();
const mockPost = await createMockTypeFn<BlogPost>();
// Generate mock data
const post = mockPost();
// Validate
if (isPost(post)) {
// Serialize to JSON (does not mutate original)
const json = stringifyPost(post);
// Deserialize
const parsed = JSON.parse(json);
const restored = restorePost(parsed);
// restored.publishedAt is a Date
// restored.metadata is a Map
} else {
const errors = getPostErrors(post);
console.log('Validation failed:', errors);
}
import {createTypeErrorsFn} from '@mionjs/run-types';
interface User {
name: string;
age: number;
}
const getUserErrors = await createTypeErrorsFn<User>();
const errors = getUserErrors({name: 123, age: 'invalid'});
// Returns: [
// { path: ['name'], expected: 'string', actual: 'number' },
// { path: ['age'], expected: 'number', actual: 'string' }
// ]
Use Number formats with specific ranges enable optimized binary serialization:
import {createToBinaryFn, createFromBinaryFn} from '@mionjs/run-types';
interface User {
name: string;
age: number;
}
// start-to-binary
const toBinary = await createToBinaryFn<User>();
const buffer = toBinary({name: 'John', age: 30});
// Returns Uint8Array with optimized binary encoding
// end-to-binary
// start-from-binary
const fromBinary = await createFromBinaryFn<User>();
const bufferInput = new Uint8Array(); // from previous example
const user = fromBinary(bufferInput);
// user is now { name: 'John', age: 30 }
// end-from-binary
Type Formats support branded types for nominal typing. This prevents accidental assignment of plain strings/numbers to validated types, providing compile-time type safety.
Most built-in formats use branded types with fixed brand names for several important reasons:
FormatUUIDv4 → uuid column type)Most built-in string formats are branded by default:
import type {FormatEmail, FormatUUIDv4} from '@mionjs/type-formats/StringFormats';
type User = {
email: FormatEmail; // Branded with 'email'
id: FormatUUIDv4; // Branded with 'uuid'
};
// ❌ TypeScript Error: Type 'string' is not assignable to type 'FormatEmail'
const user: User = {
email: 'test@example.com', // Error!
id: '550e8400-e29b-41d4-a716-446655440000' // Error!
};
To assign values to branded types, use type assertions:
import type {BrandEmail, BrandUUID} from '@mionjs/core';
const user: User = {
email: 'test@example.com' as BrandEmail,
id: '550e8400-e29b-41d4-a716-446655440000' as BrandUUID,
};
FormatString, FormatNumber, FormatBigIntFormatString, FormatNumber, and FormatBigInt are unbranded by default (escape hatch), but you can add branding:
import type {FormatString} from '@mionjs/type-formats/StringFormats';
import type {FormatNumber} from '@mionjs/type-formats/NumberFormats';
// Unbranded - accepts plain strings/numbers
type DisplayName = FormatString<{minLength: 2; maxLength: 50}>;
type Age = FormatNumber<{min: 0; max: 150; integer: true}>;
// Branded - requires type assertion
type UserName = FormatString<{minLength: 2; maxLength: 50}, 'UserName'>;
type UserId = FormatNumber<{min: 1; integer: true}, 'UserId'>;
Note: Built-in formats like
FormatEmail,FormatUUIDv4,FormatUrl, etc. have fixed brand names that cannot be overridden. This ensures consistent type mapping across the framework.
Client code should not depend on @mionjs/type-formats (which includes runtime validation code). Instead, import branded types from @mionjs/core:
| Server Type (type-formats) | Client Type (core) |
|---|---|
FormatEmail | BrandEmail |
FormatUUIDv4 | BrandUUID |
FormatUUIDv7 | BrandUUID |
FormatUrl, FormatUrlHttp, FormatUrlFile, FormatUrlSocialMedia | BrandUrl |
FormatDomain, FormatDomainStrict | BrandDomain |
FormatIP, FormatIPv4, FormatIPv6, etc. | BrandIP |
FormatStringDate | BrandDate |
FormatStringTime | BrandTime |
FormatStringDateTime | BrandDateTime |
FormatString | ❌ (unbranded by default) |
FormatNumber | ❌ (unbranded by default) |
FormatBigInt | ❌ (unbranded by default) |
// client-types.ts
import type {BrandEmail, BrandUUID, BrandUrl} from '@mionjs/core';
// These types are compatible with server-side FormatEmail, FormatUUIDv4, etc.
type User = {
id: BrandUUID;
email: BrandEmail;
website?: BrandUrl;
};
// Usage
const user: User = {
id: '550e8400-e29b-41d4-a716-446655440000' as BrandUUID,
email: 'user@example.com' as BrandEmail,
website: 'https://example.com' as BrandUrl,
};