transmutant
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -29,9 +29,6 @@ "use strict"; | ||
*/ | ||
const transmute = (schema, source, extra) => schema.reduce((acc, { from, to, fn }) => { | ||
var _a; | ||
return ({ | ||
...acc, | ||
[to]: fn ? fn({ source, extra }) : (_a = source[from]) !== null && _a !== void 0 ? _a : null | ||
}); | ||
}, {}); | ||
const transmute = (schema, source, extra) => schema.reduce((acc, { from, to }) => ({ | ||
...acc, | ||
[to]: typeof from === 'function' ? from({ source, extra }) : source[from] | ||
}), {}); | ||
exports.transmute = transmute; |
/** | ||
* Arguments passed to a mutation function | ||
* @template From - The source type being transmuted from | ||
* @template Source - The source type being transmuted from | ||
* @template TExtra - Type of additional data for transmutation | ||
*/ | ||
@@ -13,21 +14,21 @@ export type TransmuteFnArgs<Source, TExtra> = { | ||
* Function that performs a custom transmutation on a source object | ||
* @template From - The source type being transmuted from | ||
* @template Source - The source type being transmuted from | ||
* @template Target - The target type being transmuted to | ||
* @template TargetKey - The specific key of the target property being set | ||
* @template TExtra - Type of additional data for transmutation | ||
*/ | ||
export type TransmuteFn<Source, TExtra = unknown> = (args: TransmuteFnArgs<Source, TExtra>) => unknown; | ||
export type TransmuteFn<Source, Target, TargetKey extends keyof Target, TExtra = unknown> = (args: TransmuteFnArgs<Source, TExtra>) => Target[TargetKey]; | ||
/** | ||
* Defines how a property should be transmuted from source to target type | ||
* @template From - The source type being transmuted from | ||
* @template To - The target type being transmuted to | ||
* @template Source - The source type being transmuted from | ||
* @template Target - The target type being transmuted to | ||
* @template TExtra - Type of additional data for transmutation | ||
*/ | ||
export type Schema<Source, Target, TExtra = unknown> = { | ||
/** Target property key */ | ||
to: keyof Target; | ||
} & ({ | ||
/** Source property key for direct mapping */ | ||
from: keyof Source; | ||
fn?: never; | ||
} | { | ||
/** Custom transmutation function */ | ||
fn: TransmuteFn<Source, TExtra>; | ||
from?: never; | ||
}); | ||
[TargetKey in keyof Target]: { | ||
/** Target property key */ | ||
to: TargetKey; | ||
/** Source property key for direct mapping or a custom transmutation function */ | ||
from: keyof Source | TransmuteFn<Source, Target, TargetKey, TExtra>; | ||
}; | ||
}[keyof Target]; |
{ | ||
"name": "transmutant", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"main": "dist/index.js", | ||
@@ -29,2 +29,10 @@ "types": "dist/index.d.ts", | ||
"description": "Powerful type transmutations for TypeScript 🧬", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/tonioriol/transmutant.git" | ||
}, | ||
"homepage": "https://github.com/tonioriol/transmutant#readme", | ||
"bugs": { | ||
"url": "https://github.com/tonioriol/transmutant/issues" | ||
}, | ||
"devDependencies": { | ||
@@ -31,0 +39,0 @@ "@types/jest": "^29.5.14", |
327
README.md
@@ -1,5 +0,19 @@ | ||
# Transmutant | ||
# 🧬 Transmutant 🧬 | ||
A lightweight TypeScript library for flexible object transmutation with type safety. | ||
A powerful, type-safe TypeScript library for transforming objects through flexible schema definitions. | ||
[![npm version](https://badge.fury.io/js/transmutant.svg)](https://www.npmjs.com/package/transmutant) | ||
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) | ||
[![GitHub issues](https://img.shields.io/github/issues/tonioriol/transmutant)](https://github.com/tonioriol/transmutant/issues) | ||
[![GitHub stars](https://img.shields.io/github/stars/tonioriol/transmutant)](https://github.com/tonioriol/transmutant/stargazers) | ||
## Features | ||
- 🔒 **Type-safe**: Full TypeScript support with strong type inference | ||
- 🎯 **Flexible mapping**: Direct property mapping or custom transformation functions | ||
- ⚡ **High performance**: Minimal overhead and zero dependencies | ||
- 🔄 **Extensible**: Support for custom transformation logic and external data | ||
- 📦 **Lightweight**: Zero dependencies, small bundle size | ||
- 🛠️ **Predictable**: Transparent handling of undefined values | ||
## Installation | ||
@@ -11,15 +25,67 @@ | ||
## Features | ||
## Quick Start | ||
- 🔒 Type-safe transmutations | ||
- 🎯 Direct property mapping | ||
- ⚡ Custom transmutation functions | ||
- 🔄 Flexible schema definition | ||
- 📦 Zero dependencies | ||
```typescript | ||
import { transmute, Schema } from 'transmutant'; | ||
## Usage | ||
// Source type | ||
interface User { | ||
firstName: string; | ||
lastName: string; | ||
email: string; | ||
} | ||
### Direct Property Mapping | ||
// Target type | ||
interface UserDTO { | ||
fullName: string; | ||
contactEmail: string; | ||
} | ||
// Define transformation schema | ||
const schema: Schema<User, UserDTO>[] = [ | ||
{ | ||
to: 'fullName', | ||
from: ({ source }) => `${source.firstName} ${source.lastName}` | ||
}, | ||
{ | ||
from: 'email', | ||
to: 'contactEmail' | ||
} | ||
]; | ||
// Transform the object | ||
const user: User = { | ||
firstName: 'John', | ||
lastName: 'Doe', | ||
email: 'john@example.com' | ||
}; | ||
const userDTO = transmute(schema, user); | ||
// Result: { fullName: 'John Doe', contactEmail: 'john@example.com' } | ||
``` | ||
## Core Concepts | ||
### Schema Definition | ||
A schema is an array of transformation rules that define how properties should be mapped from the source to the target type. Each rule specifies the target property key and either a source property key for direct mapping or a transformation function that produces the correct type for that target property. | ||
```typescript | ||
type Schema<Source, Target, TExtra = unknown> = { | ||
[TargetKey in keyof Target]: { | ||
/** Target property key */ | ||
to: TargetKey | ||
/** Source property key for direct mapping or a custom transformation function */ | ||
from: keyof Source | TransmuteFn<Source, Target, TargetKey, TExtra> | ||
} | ||
}[keyof Target] | ||
``` | ||
### Transformation Types | ||
#### 1. Direct Property Mapping | ||
Map a property directly from source to target: | ||
```typescript | ||
interface Source { | ||
@@ -36,19 +102,15 @@ email: string; | ||
]; | ||
``` | ||
const source: Source = { email: 'john@example.com' }; | ||
const target = transmute(schema, source); | ||
#### 2. Custom Transformation Functions | ||
// Result: { contactEmail: 'john@example.com' } | ||
``` | ||
Transform properties using custom logic with type safety: | ||
### Using Custom Transmutation Functions | ||
```typescript | ||
interface Source { | ||
firstName: string; | ||
lastName: string; | ||
age: number; | ||
} | ||
interface Target { | ||
fullName: string; | ||
isAdult: boolean; | ||
} | ||
@@ -58,104 +120,211 @@ | ||
{ | ||
to: 'fullName', | ||
fn: ({ source }) => `${source.firstName} ${source.lastName}` | ||
to: 'isAdult', | ||
from: ({ source }) => source.age >= 18 | ||
} | ||
]; | ||
``` | ||
const source: Source = { firstName: 'John', lastName: 'Doe' }; | ||
const target = transmute(schema, source); | ||
#### 3. External Data Transformations | ||
// Result: { fullName: 'John Doe' } | ||
``` | ||
Include additional context in transformations: | ||
### Using Extra Data | ||
```typescript | ||
interface Source { | ||
city: string; | ||
country: string; | ||
price: number; | ||
} | ||
interface Target { | ||
location: string; | ||
formattedPrice: string; | ||
} | ||
interface Extra { | ||
separator: string; | ||
interface ExtraData { | ||
currency: string; | ||
} | ||
const schema: Schema<Source, Target, Extra>[] = [ | ||
const schema: Schema<Source, Target, ExtraData>[] = [ | ||
{ | ||
to: 'location', | ||
fn: ({ source, extra }) => | ||
`${source.city}, ${source.country}${extra?.separator}` | ||
to: 'formattedPrice', | ||
from: ({ source, extra }) => | ||
`${source.price.toFixed(2)} ${extra.currency}` | ||
} | ||
]; | ||
``` | ||
const source: Source = { | ||
city: 'New York', | ||
country: 'USA' | ||
}; | ||
### Handling Undefined Values | ||
const target = transmute(schema, source, { separator: ' | ' }); | ||
When a source property doesn't exist or a transformation function returns undefined, the target property will remain undefined: | ||
// Result: { location: 'New York, USA | ' } | ||
```typescript | ||
interface Source { | ||
existingField: string; | ||
} | ||
interface Target { | ||
mappedField: string; | ||
computedField: string; | ||
} | ||
const schema: Schema<Source, Target>[] = [ | ||
{ | ||
from: 'nonExistentField' as keyof Source, // Property doesn't exist | ||
to: 'mappedField' | ||
}, | ||
{ | ||
to: 'computedField', | ||
from: ({ source }) => undefined // Transformation returns undefined | ||
} | ||
]; | ||
const result = transmute(schema, { existingField: 'value' }); | ||
// Result: { mappedField: undefined, computedField: undefined } | ||
``` | ||
This allows you to: | ||
- Distinguish between unset values (`undefined`) and explicitly set null values | ||
- Handle optional properties naturally | ||
- Process partial transformations as needed | ||
## API Reference | ||
### `transmute(schema, source, extra?)` | ||
### `transmute<Source, Target, TExtra = unknown>` | ||
Transmutes a source object into a target type based on the provided schema. | ||
Main transformation function. | ||
#### Parameters | ||
- `schema`: Array of transmutation rules defining how properties should be transmuted | ||
- `source`: Source object to transmute | ||
- `extra`: (Optional) Additional data to pass to transmutation functions | ||
| Parameter | Type | Description | | ||
|-----------|------------------------------------|-------------------------------| | ||
| schema | `Schema<Source, Target, TExtra>[]` | Array of transformation rules | | ||
| source | `Source` | Source object to transform | | ||
| extra? | `TExtra` | Optional additional data | | ||
#### Schema Types | ||
#### Returns | ||
Each schema entry must specify the target property and use either direct mapping OR a custom function: | ||
Returns an object of type `Target`. | ||
### Type Definitions | ||
```typescript | ||
type Schema<Source, Target, TExtra> = { | ||
/** Target property key */ | ||
to: keyof Target; | ||
} & ( | ||
| { | ||
/** Source property key for direct mapping */ | ||
from: keyof Source; | ||
fn?: never; | ||
} | ||
| { | ||
/** Custom transmutation function */ | ||
fn: TransmuteFn<Source, TExtra>; | ||
from?: never; | ||
} | ||
); | ||
/** | ||
* Schema entry defining how a property should be transformed | ||
*/ | ||
type Schema<Source, Target, TExtra = unknown> = { | ||
[TargetKey in keyof Target]: { | ||
/** Target property key */ | ||
to: TargetKey | ||
/** Source property key for direct mapping or a custom transformation function */ | ||
from: keyof Source | TransmuteFn<Source, Target, TargetKey, TExtra> | ||
} | ||
}[keyof Target] | ||
/** | ||
* Function that performs property transformation | ||
*/ | ||
type TransmuteFn<Source, Target, TargetKey extends keyof Target, TExtra = unknown> = | ||
(args: TransmuteFnArgs<Source, TExtra>) => Target[TargetKey] | ||
/** | ||
* Arguments passed to transformation function | ||
*/ | ||
type TransmuteFnArgs<Source, TExtra> = { | ||
source: Source | ||
extra?: TExtra | ||
} | ||
``` | ||
The `TransmuteFn` type is defined as: | ||
### Type Safety Examples | ||
```typescript | ||
type TransmuteFn<Source, TExtra> = (args: { | ||
source: Source; | ||
extra?: TExtra; | ||
}) => unknown; | ||
interface Source { | ||
firstName: string; | ||
lastName: string; | ||
age: number; | ||
} | ||
interface Target { | ||
fullName: string; // TargetKey = 'fullName', type = string | ||
isAdult: boolean; // TargetKey = 'isAdult', type = boolean | ||
} | ||
// TypeScript enforces correct return types | ||
const schema: Schema<Source, Target>[] = [ | ||
{ | ||
to: 'fullName', | ||
from: ({ source }) => `${source.firstName} ${source.lastName}` // Must return string | ||
}, | ||
{ | ||
to: 'isAdult', | ||
from: ({ source }) => source.age >= 18 // Must return boolean | ||
} | ||
]; | ||
// Type error example: | ||
const invalidSchema: Schema<Source, Target>[] = [ | ||
{ | ||
to: 'isAdult', | ||
from: ({ source }) => source.age // Type error: number is not assignable to boolean | ||
} | ||
]; | ||
``` | ||
#### Behavior Notes | ||
### Direct Property Mapping | ||
- Direct mapping uses the `from` property to copy values directly from source to target | ||
- Custom functions receive the entire source object and optional extra data | ||
- If a direct mapping property is undefined or null, it will be set to `null` in the target object | ||
- Empty schemas result in an empty object | ||
- Each schema entry must use either `from` OR `fn`, but not both | ||
- The schema is processed sequentially, with each rule contributing to the final object | ||
When using direct property mapping, TypeScript ensures type compatibility: | ||
## License | ||
```typescript | ||
interface Source { | ||
email: string; | ||
age: number; | ||
} | ||
MIT | ||
interface Target { | ||
contactEmail: string; | ||
yearOfBirth: number; | ||
} | ||
const schema: Schema<Source, Target>[] = [ | ||
{ from: 'email', to: 'contactEmail' }, // OK: string -> string | ||
{ from: 'age', to: 'yearOfBirth' } // OK: number -> number | ||
]; | ||
``` | ||
### Using Extra Data | ||
Extra data is fully typed: | ||
```typescript | ||
interface ExtraData { | ||
currentYear: number; | ||
} | ||
interface Source { | ||
age: number; | ||
} | ||
interface Target { | ||
yearOfBirth: number; | ||
} | ||
const schema: Schema<Source, Target, ExtraData>[] = [ | ||
{ | ||
to: 'yearOfBirth', | ||
from: ({ source, extra }) => extra.currentYear - source.age | ||
} | ||
]; | ||
const result = transmute(schema, { age: 25 }, { currentYear: 2024 }); | ||
``` | ||
## Contributing | ||
Contributions are welcome! Please feel free to submit a Pull Request. | ||
Contributions are welcome! Feel free to submit a Pull Request. | ||
## License | ||
MIT © Antoni Oriol | ||
--- | ||
<div align="center"> | ||
<sub>Built with ❤️ by <a href="https://github.com/tonioriol">Antoni Oriol</a></sub> | ||
</div> |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
12403
0
0
328
0
81