-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(types): switch Extra type parameter default to undefined
BREAKING CHANGE: Changed Extra type parameter default from unknown to undefined for better type inference and runtime behavior alignment.
- Loading branch information
Showing
4 changed files
with
142 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,7 @@ npm install transmutant | |
## Quick Start | ||
|
||
```typescript | ||
import { transmute, Schema } from 'transmutant'; | ||
import { Schema, transmute } from 'transmutant' | ||
|
||
// Source type | ||
interface User { | ||
|
@@ -47,44 +47,44 @@ const schema: Schema<User, UserDTO>[] = [ | |
from: ({ source }) => `${source.firstName} ${source.lastName}` | ||
}, | ||
{ | ||
from: 'email', | ||
to: 'contactEmail' | ||
to: 'contactEmail', | ||
from: 'email' | ||
} | ||
]; | ||
] | ||
|
||
// Transmute the object | ||
const user: User = { | ||
firstName: 'John', | ||
lastName: 'Doe', | ||
email: '[email protected]' | ||
}; | ||
} | ||
|
||
const userDTO = transmute(schema, user); | ||
const userDTO = transmute(schema, user) | ||
// Result: { fullName: 'John Doe', contactEmail: '[email protected]' } | ||
``` | ||
|
||
## Core Concepts | ||
|
||
### Schema Definition | ||
|
||
A schema is an array of transmutation 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 transmuter function that produces the correct type for that target property. | ||
A schema is an array of transmutation rules that define how properties should be mapped from the source to the target | ||
type. Each rule specifies: | ||
|
||
- The target property key (`to`) | ||
- Either a source property key for direct mapping or a transmuter function (`from`) | ||
|
||
```typescript | ||
type Schema<Source, Target, Extra = unknown> = { | ||
[TargetKey in keyof Target]: { | ||
/** Target property key */ | ||
to: TargetKey | ||
/** Source property key for direct mapping or a custom transmuter function */ | ||
from: keyof Source | Transmuter<Source, Target, TargetKey, Extra> | ||
} | ||
}[keyof Target] | ||
type Schema<Source, Target, Extra> = { | ||
to: keyof Target, | ||
from: keyof Source | Transmuter<Source, Target, Extra> | ||
} | ||
``` | ||
### Transmutation Types | ||
#### 1. Direct Property Mapping | ||
Map a property directly from source to target: | ||
Map a property directly from source to target when types are compatible: | ||
```typescript | ||
interface Source { | ||
|
@@ -96,13 +96,13 @@ interface Target { | |
} | ||
|
||
const schema: Schema<Source, Target>[] = [ | ||
{ from: 'email', to: 'contactEmail' } | ||
]; | ||
{ to: 'contactEmail', from: 'email' } | ||
] | ||
``` | ||
|
||
#### 2. Custom Transmuter Functions | ||
|
||
Transmute properties using custom logic with type safety: | ||
Transmute properties using custom logic with full type safety: | ||
|
||
```typescript | ||
interface Source { | ||
|
@@ -123,114 +123,83 @@ const schema: Schema<Source, Target>[] = [ | |
|
||
#### 3. External Data Transmutations | ||
|
||
Include additional context in transmutations: | ||
Include additional context in transmutations through the `extra` parameter: | ||
|
||
```typescript | ||
interface Source { | ||
price: number; | ||
city: string; | ||
country: string; | ||
} | ||
|
||
interface Target { | ||
formattedPrice: string; | ||
location: string; | ||
} | ||
|
||
interface ExtraData { | ||
currency: string; | ||
separator: string; | ||
} | ||
|
||
const schema: Schema<Source, Target, ExtraData>[] = [ | ||
{ | ||
to: 'formattedPrice', | ||
to: 'location', | ||
from: ({ source, extra }) => | ||
`${source.price.toFixed(2)} ${extra.currency}` | ||
`${source.city}${extra.separator}${source.country}` | ||
} | ||
]; | ||
|
||
const result = transmute(schema, | ||
{ city: 'New York', country: 'USA' }, | ||
{ separator: ', ' } | ||
); | ||
// Result: { location: 'New York, USA' } | ||
``` | ||
|
||
### Handling Undefined Values | ||
## API Reference | ||
|
||
When a source property doesn't exist or a transmuter function returns undefined, the target property will remain undefined: | ||
### Types | ||
|
||
```typescript | ||
interface Source { | ||
existingField: string; | ||
} | ||
// Arguments passed to a mutation function | ||
type TransmuterArgs<Source, Extra> = { source: Source, extra?: Extra } | ||
|
||
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 // Transmutation returns undefined | ||
} | ||
]; | ||
// Function that performs a custom transmutation | ||
type Transmuter<Source, Target, Extra> = (args: TransmuterArgs<Source, Extra>) => Target[keyof Target] | ||
|
||
const result = transmute(schema, { existingField: 'value' }); | ||
// Result: { mappedField: undefined, computedField: undefined } | ||
// Defines how a property should be transmuted | ||
type Schema<Source, Target, Extra> = { | ||
to: keyof Target, | ||
from: keyof Source | Transmuter<Source, Target, Extra> | ||
} | ||
``` | ||
This allows you to: | ||
- Distinguish between unset values (`undefined`) and explicitly set null values | ||
- Handle optional properties naturally | ||
- Process partial transmutations as needed | ||
### transmute() | ||
## API Reference | ||
Main function for performing object transmutations. | ||
### `transmute<Source, Target, Extra = unknown>` | ||
|
||
Main transmuter function. | ||
```typescript | ||
function transmute<Source, Target, Extra>( | ||
schema: Schema<Source, Target, Extra>[], | ||
source: Source, | ||
extra?: Extra | ||
): Target; | ||
``` | ||
|
||
#### Parameters | ||
|
||
| Parameter | Type | Description | | ||
|-----------|-----------------------------------|------------------------------| | ||
| schema | `Schema<Source, Target, Extra>[]` | Array of transmutation rules | | ||
| source | `Source` | Source object to transmut | | ||
| extra? | `Extra` | Optional additional data | | ||
| Parameter | Type | Description | | ||
|-----------|-----------------------------------|------------------------------------| | ||
| schema | `Schema<Source, Target, Extra>[]` | Array of transmutation rules | | ||
| source | `Source` | Source object to transmute | | ||
| extra | `Extra` (optional) | Additional data for transmutations | | ||
|
||
#### Returns | ||
|
||
Returns an object of type `Target`. | ||
|
||
### Type Definitions | ||
|
||
```typescript | ||
/** | ||
* Schema entry defining how a property should be transmuted | ||
*/ | ||
type Schema<Source, Target, Extra = unknown> = { | ||
[TargetKey in keyof Target]: { | ||
/** Target property key */ | ||
to: TargetKey | ||
/** Source property key for direct mapping or a custom transmuter function */ | ||
from: keyof Source | Transmuter<Source, Target, TargetKey, Extra> | ||
} | ||
}[keyof Target] | ||
|
||
/** | ||
* Function that performs property transmutation | ||
*/ | ||
type Transmuter<Source, Target, TargetKey extends keyof Target, Extra = unknown> = | ||
(args: TransmuterArgs<Source, Extra>) => Target[TargetKey] | ||
|
||
/** | ||
* Arguments passed to transmuter function | ||
*/ | ||
type TransmuterArgs<Source, Extra> = { | ||
source: Source | ||
extra?: Extra | ||
} | ||
``` | ||
Returns an object of type `Target` with all specified transmutations applied. | ||
|
||
### Type Safety Examples | ||
|
||
The library provides strong type checking for both direct mappings and custom transmutations: | ||
|
||
```typescript | ||
interface Source { | ||
firstName: string; | ||
|
@@ -239,79 +208,31 @@ interface Source { | |
} | ||
|
||
interface Target { | ||
fullName: string; // TargetKey = 'fullName', type = string | ||
isAdult: boolean; // TargetKey = 'isAdult', type = boolean | ||
fullName: string; | ||
isAdult: boolean; | ||
} | ||
|
||
// TypeScript enforces correct return types | ||
const schema: Schema<Source, Target>[] = [ | ||
// Correct usage - types match | ||
const validSchema: Schema<Source, Target>[] = [ | ||
{ | ||
to: 'fullName', | ||
from: ({ source }) => `${source.firstName} ${source.lastName}` // Must return string | ||
from: ({ source }) => `${source.firstName} ${source.lastName}` // Returns string | ||
}, | ||
{ | ||
to: 'isAdult', | ||
from: ({ source }) => source.age >= 18 // Must return boolean | ||
from: ({ source }) => source.age >= 18 // Returns boolean | ||
} | ||
]; | ||
|
||
// Type error example: | ||
// Type error - incorrect return type | ||
const invalidSchema: Schema<Source, Target>[] = [ | ||
{ | ||
to: 'isAdult', | ||
from: ({ source }) => source.age // Type error: number is not assignable to boolean | ||
from: ({ source }) => source.age // Error: number not assignable to boolean | ||
} | ||
]; | ||
``` | ||
|
||
### Direct Property Mapping | ||
|
||
When using direct property mapping, TypeScript ensures type compatibility: | ||
|
||
```typescript | ||
interface Source { | ||
email: string; | ||
age: number; | ||
} | ||
|
||
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! Feel free to submit a Pull Request. | ||
|
Oops, something went wrong.