diff --git a/CHANGELOG.md b/CHANGELOG.md index 9843ad6..929bf98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Features -* enforce strict return types in transformation functions ([bb575df](https://github.com/tonioriol/transmutant/commit/bb575dfd605934d76627867ed507567591c86317)) +* enforce strict return types in transmutation functions ([bb575df](https://github.com/tonioriol/transmutant/commit/bb575dfd605934d76627867ed507567591c86317)) ## [2.0.0](https://github.com/tonioriol/transmutant/compare/v1.0.1...v2.0.0) (2024-10-28) diff --git a/README.md b/README.md index ba7f6ba..36c3808 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🧬 Transmutant 🧬 -A powerful, type-safe TypeScript library for transforming objects through flexible schema definitions. +A powerful, type-safe TypeScript library for transmuting 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) @@ -10,9 +10,9 @@ A powerful, type-safe TypeScript library for transforming objects through flexib ## Features - 🔒 **Type-safe**: Full TypeScript support with strong type inference -- 🎯 **Flexible mapping**: Direct property mapping or custom transformation functions +- 🎯 **Flexible mapping**: Direct property mapping or custom transmutation functions - ⚡ **High performance**: Minimal overhead and zero dependencies -- 🔄 **Extensible**: Support for custom transformation logic and external data +- 🔄 **Extensible**: Support for custom transmutation logic and external data - 📦 **Lightweight**: Zero dependencies, small bundle size - 🛠️ **Predictable**: Transparent handling of undefined values @@ -40,7 +40,7 @@ interface UserDTO { contactEmail: string; } -// Define transformation schema +// Define transmutation schema const schema: Schema[] = [ { to: 'fullName', @@ -52,7 +52,7 @@ const schema: Schema[] = [ } ]; -// Transform the object +// Transmut the object const user: User = { firstName: 'John', lastName: 'Doe', @@ -67,20 +67,20 @@ const userDTO = transmute(schema, user); ### 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. +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 transmutation function that produces the correct type for that target property. ```typescript -type Schema = { +type Schema = { [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 property key for direct mapping or a custom transmutation function */ + from: keyof Source | TransmuteFn } }[keyof Target] ``` -### Transformation Types +### Transmutation Types #### 1. Direct Property Mapping @@ -100,9 +100,9 @@ const schema: Schema[] = [ ]; ``` -#### 2. Custom Transformation Functions +#### 2. Custom Transmutation Functions -Transform properties using custom logic with type safety: +Transmute properties using custom logic with type safety: ```typescript interface Source { @@ -121,9 +121,9 @@ const schema: Schema[] = [ ]; ``` -#### 3. External Data Transformations +#### 3. External Data Transmutations -Include additional context in transformations: +Include additional context in transmutations: ```typescript interface Source { @@ -149,7 +149,7 @@ const schema: Schema[] = [ ### Handling Undefined Values -When a source property doesn't exist or a transformation function returns undefined, the target property will remain undefined: +When a source property doesn't exist or a transmutation function returns undefined, the target property will remain undefined: ```typescript interface Source { @@ -168,7 +168,7 @@ const schema: Schema[] = [ }, { to: 'computedField', - from: ({ source }) => undefined // Transformation returns undefined + from: ({ source }) => undefined // Transmutation returns undefined } ]; @@ -179,21 +179,21 @@ const result = transmute(schema, { existingField: 'value' }); This allows you to: - Distinguish between unset values (`undefined`) and explicitly set null values - Handle optional properties naturally -- Process partial transformations as needed +- Process partial transmutations as needed ## API Reference -### `transmute` +### `transmute` -Main transformation function. +Main transmutation function. #### Parameters -| Parameter | Type | Description | -|-----------|------------------------------------|-------------------------------| -| schema | `Schema[]` | Array of transformation rules | -| source | `Source` | Source object to transform | -| extra? | `TExtra` | Optional additional data | +| Parameter | Type | Description | +|-----------|-----------------------------------|------------------------------| +| schema | `Schema[]` | Array of transmutation rules | +| source | `Source` | Source object to transmut | +| extra? | `Extra` | Optional additional data | #### Returns @@ -203,29 +203,29 @@ Returns an object of type `Target`. ```typescript /** - * Schema entry defining how a property should be transformed + * Schema entry defining how a property should be transmuted */ -type Schema = { +type Schema = { [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 property key for direct mapping or a custom transmutation function */ + from: keyof Source | TransmuteFn } }[keyof Target] /** - * Function that performs property transformation + * Function that performs property transmutation */ -type TransmuteFn = - (args: TransmuteFnArgs) => Target[TargetKey] +type TransmuteFn = + (args: TransmuteFnArgs) => Target[TargetKey] /** - * Arguments passed to transformation function + * Arguments passed to transmutation function */ -type TransmuteFnArgs = { +type TransmuteFnArgs = { source: Source - extra?: TExtra + extra?: Extra } ``` diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index dec69a3..b5e01a4 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -47,31 +47,26 @@ describe('transmute', () => { { to: 'fullName', from: ({ source }) => `${source.firstName} ${source.lastName}` - } - ] - - const result = transmute(schema, sourceUser) - expect(result).toEqual({ fullName: 'John Doe' }) - }) - - it('should handle transmutation with both "from" and "fn"', () => { - const schema: Schema[] = [ + }, { - to: 'userAge', - from: ({ source }) => source['age'] + 1 + to: 'isAdult', + from: ({ source }) => source.age >= 18 } ] const result = transmute(schema, sourceUser) - expect(result).toEqual({ userAge: 26 }) + expect(result).toEqual({ + fullName: 'John Doe', + isAdult: true + }) }) - it('should handle extra data in transmutations', () => { + it('should handle transmutation with extra data', () => { interface Extra { - 'separator': string + separator: string } - const schema: Schema[] = [ + const schema: Schema, Extra>[] = [ { to: 'location', from: ({ source, extra }) => @@ -83,7 +78,7 @@ describe('transmute', () => { expect(result).toEqual({ location: 'New York, USA | ' }) }) - it('should handle multiple transmutations', () => { + it('should handle complete object transmutation', () => { const schema: Schema[] = [ { to: 'fullName', @@ -117,36 +112,4 @@ describe('transmute', () => { isAdult: true }) }) - - it('should keep undefined values as undefined', () => { - const schema: Schema[] = [ - { - to: 'optionalField', - from: 'nonexistentField' as keyof SourceUser - } - ] - - const result = transmute(schema, sourceUser) - expect(result).toEqual({ optionalField: undefined }) - }) - - it('should handle empty schema', () => { - const schema: Schema[] = [] - const result = transmute(schema, sourceUser) - expect(result).toEqual({}) - }) - - it('should handle null source values', () => { - const sourceWithNull = { - ...sourceUser, - email: null as unknown as string - } - - const schema: Schema>[] = [ - { from: 'email', to: 'contactEmail' } - ] - - const result = transmute(schema, sourceWithNull) - expect(result).toEqual({ contactEmail: null }) - }) }) diff --git a/src/index.ts b/src/index.ts index 08b3fc4..6dfd00a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,16 +6,16 @@ export * from './types' * Transmutes an object from the Source type into the Target type based on the provided schema * @template Source - The source type being transmuted from * @template Target - The target type being transmuted to - * @template TExtra - Type of additional data passed to mutation functions + * @template Extra - Type of additional data passed to mutation functions * @param schema - Array of mutation rules * @param source - Source object to transmute * @param extra - Optional extra data to pass to mutation functions * @returns Transmuted object matching Target type */ -export const transmute = ( - schema: Schema[], +export const transmute = ( + schema: Schema[], source: Source, - extra?: TExtra + extra?: Extra ): Target => schema.reduce( (acc, { from, to }) => ({ diff --git a/src/types.ts b/src/types.ts index 5b50598..d332101 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,13 +1,13 @@ /** * Arguments passed to a mutation function * @template Source - The source type being transmuted from - * @template TExtra - Type of additional data for transmutation + * @template Extra - Type of additional data for transmutation */ -export type TransmuteFnArgs = { +export type TransmuteFnArgs = { /** The source object being transmuted */ source: Source /** Optional extra data to assist with transmutation */ - extra?: TExtra + extra?: Extra } /** @@ -15,22 +15,29 @@ export type TransmuteFnArgs = { * @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 + * @template Extra - Type of additional data for transmutation */ -export type TransmuteFn = - (args: TransmuteFnArgs) => Target[TargetKey] +export type TransmuteFn = + (args: TransmuteFnArgs) => Target[TargetKey] + +/** + * Get keys of Source that have values assignable to Target[TargetKey] + */ +type AssignableKeys = { + [SourceKey in keyof Source]: Source[SourceKey] extends Target[TargetKey] ? SourceKey : never +}[keyof Source] /** * Defines how a property should be transmuted from source to target type * @template Source - The source type being transmuted from * @template Target - The target type being transmuted to - * @template TExtra - Type of additional data for transmutation + * @template Extra - Type of additional data for transmutation */ -export type Schema = { +export type Schema = { [TargetKey in keyof Target]: { /** Target property key */ to: TargetKey /** Source property key for direct mapping or a custom transmutation function */ - from: keyof Source | TransmuteFn + from: AssignableKeys | TransmuteFn } }[keyof Target]