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 @mionkit/type-formats/FormatsString:
import {StrEmail, StrEmailStrict, StrEmailPunycode} from '@mionkit/type-formats/FormatsString';
import {StrEmail} from '@mionkit/type-formats/FormatsString';
type UserEmail = StrEmail;
// 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)
StrFormatUse StrFormat to define custom string constraints:
import {StrFormat} from '@mionkit/type-formats/FormatsString';
// Username: 3-20 chars, lowercase, trimmed
type Username = StrFormat<{
minLength: 3;
maxLength: 20;
lowercase: true;
trim: true;
}>;
// Slug with pattern validation
const slugRegex = /^[a-z0-9-]+$/;
type Slug = StrFormat<{
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 = StrFormat<{
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 @mionkit/type-formats/FormatsNumber:
import {
NumFormat,
NumInteger,
NumFloat,
NumPositive,
NumNegative,
NumPositiveInt,
NumNegativeInt,
NumInt8,
NumInt16,
NumInt32,
NumUInt8,
NumUInt16,
NumUInt32,
} from '@mionkit/type-formats/FormatsNumber';
import {NumFormat} from '@mionkit/type-formats/FormatsNumber';
// Age with valid range
type Age = NumFormat<{
min: 0;
max: 120;
integer: true;
}>;
// Percentage with decimals
type Percentage = NumFormat<{
min: 0;
max: 100;
}>;
// Price must be multiple of 0.01 (cents)
type Price = NumFormat<{
min: 0;
multipleOf: 1; // multipleOf must be integer
integer: true; // store as cents
}>;
NumFormatimport {NumFormat} from '@mionkit/type-formats/FormatsNumber';
// Age with valid range
type Age = NumFormat<{
min: 0;
max: 120;
integer: true;
}>;
// Percentage with decimals
type Percentage = NumFormat<{
min: 0;
max: 100;
}>;
// Price must be multiple of 0.01 (cents)
type Price = NumFormat<{
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 '@mionkit/run-types';
interface BlogPost {
id: string;
title: string;
content: string;
author: {
name: string;
email: string;
};
tags: string[];
publishedAt: Date;
metadata: Map<string, any>;
}
async function completeExample() {
// Create all needed functions
const isPost = await createIsTypeFn<BlogPost>();
const getErrors = await createTypeErrorsFn<BlogPost>();
const stringify = await createStringifyJsonFn<BlogPost>();
const restore = 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 = stringify(post);
// Deserialize
const parsed = JSON.parse(json);
const restored = restore(parsed);
// restored.publishedAt is a Date
// restored.metadata is a Map
} else {
const errors = getErrors(post);
console.log('Validation failed:', errors);
}
}
import {createTypeErrorsFn} from '@mionkit/run-types';
interface User {
name: string;
age: number;
}
async function example() {
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 '@mionkit/run-types';
interface User {
name: string;
age: number;
}
// start-to-binary
async function toBinaryExample() {
const toBinary = await createToBinaryFn<User>();
const buffer = toBinary({name: 'John', age: 30});
// Returns Uint8Array with optimized binary encoding
}
// end-to-binary
// start-from-binary
async function fromBinaryExample() {
const fromBinary = await createFromBinaryFn<User>();
const buffer = new Uint8Array(); // from previous example
const user = fromBinary(buffer);
// 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:
StrUUIDv4 → uuid column type)Most built-in string formats are branded by default:
import type {StrEmail, StrUUIDv4} from '@mionkit/type-formats/FormatsString';
type User = {
email: StrEmail; // Branded with 'email'
id: StrUUIDv4; // Branded with 'uuid'
};
// ❌ TypeScript Error: Type 'string' is not assignable to type 'StrEmail'
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 '@mionkit/core';
const user: User = {
email: 'test@example.com' as BrandEmail,
id: '550e8400-e29b-41d4-a716-446655440000' as BrandUUID,
};
StrFormat, NumFormat, BigNumFormatStrFormat, NumFormat, and BigNumFormat are unbranded by default (escape hatch), but you can add branding:
import type {StrFormat} from '@mionkit/type-formats/FormatsString';
import type {NumFormat} from '@mionkit/type-formats/FormatsNumber';
// Unbranded - accepts plain strings/numbers
type DisplayName = StrFormat<{minLength: 2; maxLength: 50}>;
type Age = NumFormat<{min: 0; max: 150; integer: true}>;
// Branded - requires type assertion
type UserName = StrFormat<{minLength: 2; maxLength: 50}, 'UserName'>;
type UserId = NumFormat<{min: 1; integer: true}, 'UserId'>;
Note: Built-in formats like
StrEmail,StrUUIDv4,StrUrl, etc. have fixed brand names that cannot be overridden. This ensures consistent type mapping across the framework.
Client code should not depend on @mionkit/type-formats (which includes runtime validation code). Instead, import branded types from @mionkit/core:
| Server Type (type-formats) | Client Type (core) |
|---|---|
StrEmail | BrandEmail |
StrUUIDv4 | BrandUUID |
StrUUIDv7 | BrandUUID |
StrUrl, StrUrlHttp, StrUrlFile, StrUrlSocialMedia | BrandUrl |
StrDomain, StrDomainStrict | BrandDomain |
StrIP, StrIPv4, StrIPv6, etc. | BrandIP |
StrDate | BrandDate |
StrTime | BrandTime |
StrDateTime | BrandDateTime |
StrFormat | ❌ (unbranded by default) |
NumFormat | ❌ (unbranded by default) |
BigNumFormat | ❌ (unbranded by default) |
// client-types.ts
import type {BrandEmail, BrandUUID, BrandUrl} from '@mionkit/core';
// These types are compatible with server-side StrEmail, StrUUIDv4, 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,
};