@mahmoudxyz/hassan
Version:
Type-safe data mapper for TypeScript with zero runtime overhead
315 lines (242 loc) • 7.42 kB
Markdown
# Hassan
[](#contributors-)
[](https://badge.fury.io/js/hassan)
> Type-safe data mapper for TypeScript with zero runtime overhead
## Quick Start
```bash
npm install @mahmoudxyz/hassan
```
```typescript
import { createMapper, h } from 'hassan'
// Simple property mapping
const userMapper = createMapper({
name: 'firstName',
email: 'userEmail',
})
const input = {
firstName: 'John',
userEmail: 'john@example.com',
age: 30,
}
const result = userMapper.map(input)
// { name: 'John', email: 'john@example.com' }
```
## Features
- ✅ **100% Type Safe** - Complete TypeScript type inference
- ✅ **Zero Runtime Overhead** - Optimized mapping functions
- ✅ **TDD Driven** - Built with comprehensive test coverage
- ✅ **Simple API** - Intuitive and discoverable interface
- ✅ **Nested Objects** - Deep property mapping support
- ✅ **Conditional Logic** - Context-aware transformations
- ✅ **Transform Functions** - Custom data transformations
## Usage Examples
### Basic Property Mapping
```typescript
const mapper = createMapper({
name: 'firstName',
email: 'userEmail',
})
mapper.map({ firstName: 'John', userEmail: 'john@example.com' })
// { name: 'John', email: 'john@example.com' }
```
### Direct Values
```typescript
const mapper = createMapper({
version: h.direct('1.0'),
isActive: h.direct(true),
count: h.direct(42),
})
mapper.map({})
// { version: '1.0', isActive: true, count: 42 }
```
### Transform Functions
```typescript
const mapper = createMapper({
fullName: source => `${source.firstName} ${source.lastName}`,
age: source => new Date().getFullYear() - source.birthYear,
upperEmail: source => source.email.toUpperCase(),
})
const input = {
firstName: 'John',
lastName: 'Doe',
birthYear: 1990,
email: 'john@example.com',
}
mapper.map(input)
// {
// fullName: 'John Doe',
// age: 34,
// upperEmail: 'JOHN@EXAMPLE.COM'
// }
```
### Conditional Logic
```typescript
const mapper = createMapper({
status: h.when(
source => source.isActive,
h.direct('ACTIVE'),
h.direct('INACTIVE')
),
grade: h.when(
source => source.score >= 90,
() => 'A',
source => (source.score >= 80 ? 'B' : 'C')
),
})
mapper.map({ isActive: true, score: 95 })
// { status: 'ACTIVE', grade: 'A' }
```
### Nested Objects
```typescript
// Source nesting
const mapper = createMapper({
city: 'address.city',
country: 'address.location.country',
})
// Target nesting
const mapper = createMapper({
'user.name': 'firstName',
'user.profile.email': 'email',
'user.settings.theme': h.direct('dark'),
})
const input = { firstName: 'John', email: 'john@example.com' }
mapper.map(input)
// {
// user: {
// name: 'John',
// profile: { email: 'john@example.com' },
// settings: { theme: 'dark' }
// }
// }
```
### Context-Aware Transformations
```typescript
const mapper = createMapper({
greeting: (source, context) =>
`Hello ${source.name}, today is ${context?.date}`,
accessLevel: h.when(
(source, context) => context?.currentUser?.role === 'admin',
h.direct('FULL_ACCESS'),
h.direct('LIMITED_ACCESS')
),
})
const input = { name: 'John' }
const context = {
date: '2024-01-15',
currentUser: { role: 'admin' },
}
mapper.map(input, context)
// {
// greeting: 'Hello John, today is 2024-01-15',
// accessLevel: 'FULL_ACCESS'
// }
```
### Complex Real-World Example
```typescript
const userMapper = createMapper({
// Simple mapping
id: 'userId',
// Direct values
version: h.direct('1.0'),
// Transformations
fullName: source => `${source.firstName} ${source.lastName}`,
// Nested source
country: 'address.country',
// Nested target
'profile.displayName': source => source.firstName.toUpperCase(),
// Context-aware conditionals
'permissions.canEdit': h.when(
(source, context) => context?.currentUser?.id === source.userId,
h.direct(true),
h.direct(false)
),
// Complex conditional logic
'status.level': h.when(
source => source.profile?.premium,
source => source.profile?.tier || 'gold',
h.direct('basic')
),
})
const input = {
userId: 123,
firstName: 'John',
lastName: 'Doe',
address: { country: 'USA' },
profile: { premium: true, tier: 'platinum' },
}
const context = { currentUser: { id: 123 } }
userMapper.map(input, context)
// {
// id: 123,
// version: '1.0',
// fullName: 'John Doe',
// country: 'USA',
// profile: { displayName: 'JOHN' },
// permissions: { canEdit: true },
// status: { level: 'platinum' }
// }
```
## API Reference
### `createMapper(mappingConfig)`
Creates a new mapper instance with the given configuration.
### `h.direct(value)`
Maps to a direct/constant value.
### `h.when(condition, thenValue, elseValue?)`
Conditional mapping based on a predicate function.
- `condition`: `(source, context?) => boolean`
- `thenValue`: Value or function to use when condition is true
- `elseValue`: Optional value or function to use when condition is false
### Transform Functions
Use functions for custom transformations:
```typescript
;(source, context?) => any
```
## Development
This project follows Test-Driven Development (TDD) practices.
### Setup
```bash
git clone https://github.com/mahmoudxyz/hassan.git
cd hassan
npm install
```
### Available Scripts
```bash
npm test # Run tests
npm run test:watch # Run tests in watch mode
npm run test:coverage # Run tests with coverage
npm run build # Build the library
npm run lint # Lint code
npm run format # Format code
npm run type-check # Type check without emit
```
### TDD Workflow
1. Write a failing test (Red)
2. Write minimal code to make it pass (Green)
3. Refactor while keeping tests green (Refactor)
## License
MIT © Mahmoud Ahmed
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mahmoudxyz"><img src="https://avatars.githubusercontent.com/u/100426555?v=4?s=100" width="100px;" alt="Mahmoud Ahmed"/><br /><sub><b>Mahmoud Ahmed</b></sub></a><br /><a href="https://github.com/mahmoudxyz/hassan/commits?author=mahmoudxyz" title="Code">💻</a> <a href="https://github.com/mahmoudxyz/hassan/commits?author=mahmoudxyz" title="Documentation">📖</a> <a href="https://github.com/mahmoudxyz/hassan/commits?author=mahmoudxyz" title="Tests">⚠️</a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td align="center" size="13px" colspan="7">
<img src="https://raw.githubusercontent.com/all-contributors/all-contributors-cli/1b8533af435da9854653492b1327a23a4dbd0a10/assets/logo-small.svg">
<a href="https://all-contributors.js.org/docs/en/bot/usage">Add your contributions</a>
</img>
</td>
</tr>
</tfoot>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!