@belt/tools
v0.0.1
Published
TypeScript utility library with runtime type validation, state management, and common helpers
Maintainers
Readme
@belt/tools
A TypeScript utility library providing runtime type validation, state management, and common helpers.
Installation
pnpm add @belt/toolsPeer Dependencies:
react>= 18.0.0 (optional, only for state-machine hooks)
Features
- Schema Validation - Runtime type validation with TypeBox
- State Machine - Immer-based state management with React hooks
- Helpers - Common utilities (date, string, object, etc.)
- Type Utilities - Advanced TypeScript type helpers
Quick Start
Schema Validation
import { createType, createModel, isType, InferType } from '@belt/tools';
// Create a validated model
const UserModel = createModel({
id: 'number',
name: 'string',
email: 'string?', // optional
roles: 'string[]', // array
createdAt: 'date', // Date object
});
type User = InferType<typeof UserModel>;
// Parse and validate data
const user = UserModel.parse({
id: 1,
name: 'John',
roles: ['admin'],
createdAt: new Date(),
});
// Validation check
if (UserModel.isValid(someData)) {
// TypeScript knows someData is User
}
// Complex nested types
const OrderType = createType({
properties: {
customer: {
properties: {
name: 'string',
address: { properties: { city: 'string', zip: 'string' } },
},
},
items: { items: { properties: { sku: 'string', quantity: 'number' } } },
total: 'number',
},
});
// Type guard
if (isType(someValue, 'number')) {
console.log(someValue.toFixed(2)); // TypeScript knows it's a number
}State Machine
import { createMachine, createSlice, createAsyncSlice } from '@belt/tools';
// Sync slice for local state
const counterSlice = createSlice({
id: 'counter',
initial: { count: 0, lastUpdated: null as Date | null },
cache: { policy: 'session' },
reducers: {
increment: (state) => {
state.count += 1;
state.lastUpdated = new Date();
},
decrement: (state) => {
state.count -= 1;
state.lastUpdated = new Date();
},
reset: (state) => {
state.count = 0;
state.lastUpdated = null;
},
},
});
// Async slice for data fetching
const usersSlice = createAsyncSlice({
id: 'users',
cache: { policy: 'public', ttl: '5m', swr: true },
context: {},
load: async () => {
const response = await fetch('/api/users');
return response.json();
},
});
// Create the machine
const appMachine = createMachine({
id: 'app',
slices: {
counter: counterSlice,
users: usersSlice,
},
context: { userID: null },
});
// Use in React components
function Counter() {
const { state, actions } = appMachine.useMachine('counter');
return (
<div>
<span>Count: {state.count}</span>
<button onClick={() => actions.increment()}>+</button>
<button onClick={() => actions.decrement()}>-</button>
</div>
);
}Helpers
import {
getProperty,
setProperty,
ms,
formatDate,
invariant,
tryCatch,
keyBy,
compose,
} from '@belt/tools';
// Deep property access (type-safe)
const street = getProperty(user, 'address.street');
// Time conversion
const timeout = ms('5m'); // 300000 (5 minutes in ms)
const debounce = ms('300ms'); // 300
const ttl = ms('1h'); // 3600000 (1 hour)
// Date formatting (uses date-fns)
const formatted = formatDate(new Date(), 'yyyy-MM-dd');
// Assertions
invariant(user.id, 'User ID is required');
// Safe error handling
const [error, result] = tryCatch(() => JSON.parse(data));
// Array to object by key
const usersById = keyBy(users, 'id');
// Function composition
const processData = compose(
JSON.stringify,
addMetadata,
validate,
);API Reference
Schema Module
| Function | Description |
|------------------------------------|---------------------------------------|
| createType(definition) | Create a runtime validation type |
| createModel(properties) | Shorthand for object type definitions |
| isType(value, definition) | Type guard for validation |
| invariantType(definition, value) | Assert value matches type |
Type Definition Syntax:
- Primitives:
'string','number','boolean','date','null','undefined' - Optional:
'string?','number?' - Arrays:
'string[]','number[]' - Objects:
{ properties: { ... } } - Enums:
{ enum: ['a', 'b', 'c'] } - Unions:
{ union: ['string', 'number'] } - Records:
{ record: 'string' }
State Machine Module
| Function | Description |
|----------------------------|-----------------------------------------|
| createMachine(config) | Create a state machine instance |
| createSlice(config) | Create a synchronous slice |
| createAsyncSlice(config) | Create an async slice for data fetching |
Cache Policies:
public- Shared cache, TTL-based invalidationprivate- User-specific cache, invalidated on user changesession- Session-specific cache, invalidated on app restart
Machine Options:
trackMetadata- Enable state change tracking (default:false)
Helpers Module
| Function | Description |
|---------------------------------|-------------------------------------|
| getProperty(obj, path) | Type-safe deep property access |
| setProperty(obj, path, value) | Type-safe deep property setter |
| ms(timeString) | Convert time string to milliseconds |
| formatDate(date, format) | Format date using date-fns |
| invariant(condition, message) | Assert condition or throw |
| tryCatch(fn) | Safe function execution |
| compose(...fns) | Right-to-left function composition |
| keyBy(array, key) | Convert array to object by key |
| maskData(value) | Mask sensitive data for logging |
Type Utilities
import type {
Writable, // Remove readonly modifiers
Compute, // Flatten intersection types
Paths, // All dot-notation paths of object
PathType, // Type at specific path
Falsy, // Union of falsy values
InferType, // Infer type from schema definition
} from '@belt/tools';