Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tonioriol committed Oct 26, 2024
0 parents commit 6998fba
Show file tree
Hide file tree
Showing 14 changed files with 4,372 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
Empty file added .npmignore
Empty file.
195 changes: 195 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# mutant

A lightweight and flexible TypeScript library for transforming data structures using declarative schemas.

## Installation

```bash
npm install mutant
```

## Features

- 🚀 Transform objects from one shape to another using a simple schema
- 💪 Full TypeScript support with strong type inference
- 🛠️ Support for direct property mapping and custom transformation functions
- 📦 Zero dependencies
- 🔍 Additional context data support for complex transformations

## Usage

### Basic Example

```typescript
import { mutate } from 'mutant';

const schema = [
{
from: 'firstName',
to: 'givenName'
},
{
from: 'lastName',
to: 'familyName'
}
];

const source = {
firstName: 'John',
lastName: 'Doe'
};

const result = mutate(schema, source);
// Result: { givenName: 'John', familyName: 'Doe' }
```

### Custom Transformation Functions

```typescript
const schema = [
{
to: 'fullName',
fn: ({ entity }) => `${entity.firstName} ${entity.lastName}`
},
{
from: 'age',
to: 'isAdult',
fn: ({ entity }) => entity.age >= 18
}
];

const source = {
firstName: 'John',
lastName: 'Doe',
age: 25
};

const result = mutate(schema, source);
// Result: { fullName: 'John Doe', isAdult: true }
```

### Using Extra Context

```typescript
const schema = [
{
to: 'greeting',
fn: ({ entity, extra }) =>
`${extra.greeting}, ${entity.firstName}!`
}
];

const source = {
firstName: 'John'
};

const result = mutate(schema, source, { greeting: 'Hello' });
// Result: { greeting: 'Hello, John!' }
```

## API Reference

### `mutate<From, To, TExtra>(schema, entity, extra?)`

The main function for transforming data structures.

#### Parameters

- `schema`: `Schema<From, To>[]` - Array of transformation rules
- `entity`: `From` - Source object to transform
- `extra?`: `Extra` - Optional additional context data

#### Returns

- `To` - Transformed object matching the target shape

### Schema Types

#### Direct Property Mapping
```typescript
{
from: keyof From;
to: keyof To;
}
```

#### Custom Transform Function
```typescript
{
to: keyof To;
fn: (args: MutateFnArgs<From>) => unknown;
}
```

#### Combined Property Mapping and Transform
```typescript
{
from: keyof From;
to: keyof To;
fn: (args: MutateFnArgs<From>) => unknown;
}
```

### Types

#### `MutateFnArgs<From>`

Arguments passed to mutation functions:

```typescript
interface MutateFnArgs<From> {
/** Source object being mutated */
entity: From;
/** Source property key if using direct property mapping */
from?: keyof From;
/** Additional context data */
extra?: Extra;
}
```

#### `Extra`

Type for additional context data:

```typescript
type Extra = Record<string, unknown>
```
## Best Practices
1. **Type Safety**: Always define your input and output types for better type inference:
```typescript
interface Source {
firstName: string;
lastName: string;
}

interface Target {
fullName: string;
}

const schema: Schema<Source, Target>[] = [/*...*/];
```

2. **Immutability**: The library maintains immutability by default. Each transformation creates a new object.

3. **Modular Transformations**: Break down complex transformations into smaller, reusable functions:
```typescript
const formatName = ({ entity }: MutateFnArgs<Source>) =>
`${entity.firstName} ${entity.lastName}`;

const schema = [
{
to: 'fullName',
fn: formatName
}
];
```

## License

MIT

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.
83 changes: 83 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Extra, Schema } from './types';
export * from './types';
/**
* Transforms data from one shape to another using a declarative schema.
* The schema defines how properties should be mapped or transformed from
* the source object to create the target object.
*
* @typeParam From - The type of the source object
* @typeParam To - The type of the target object
* @typeParam TExtra - The type of any extra context data (defaults to Extra)
*
* @param schema - Array of transformation rules defining the mapping between source and target
* @param entity - Source object to transform
* @param extra - Optional additional context data available to transformation functions
*
* @returns A new object matching the target type specification
*
* @example
* Basic property mapping:
* ```typescript
* import { mutate } from 'mutant';
*
* const schema = [
* { from: 'firstName', to: 'givenName' },
* { from: 'lastName', to: 'familyName' }
* ];
*
* const result = mutate(schema, {
* firstName: 'John',
* lastName: 'Doe'
* });
* // Result: { givenName: 'John', familyName: 'Doe' }
* ```
*
* @example
* Using transformation functions:
* ```typescript
* import { mutate } from 'mutant';
*
* const schema = [
* {
* to: 'fullName',
* fn: ({ entity }) => `${entity.firstName} ${entity.lastName}`
* },
* {
* from: 'age',
* to: 'isAdult',
* fn: ({ entity }) => entity.age >= 18
* }
* ];
*
* const result = mutate(schema, {
* firstName: 'John',
* lastName: 'Doe',
* age: 25
* });
* // Result: { fullName: 'John Doe', isAdult: true }
* ```
*
* @example
* Using extra context:
* ```typescript
* import { mutate } from 'mutant';
*
* const schema = [
* {
* to: 'greeting',
* fn: ({ entity, extra }) =>
* `${extra.greeting}, ${entity.firstName}!`
* }
* ];
*
* const result = mutate(schema,
* { firstName: 'John' },
* { greeting: 'Hello' }
* );
* // Result: { greeting: 'Hello, John!' }
* ```
*
* @throws {TypeError} If schema or entity are null/undefined
* @throws {Error} If schema contains invalid transformation rules
*/
export declare const mutate: <From, To, TExtra extends Extra = Extra>(schema: Schema<From, To>[], entity: From, extra?: TExtra) => To;
108 changes: 108 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mutate = void 0;
__exportStar(require("./types"), exports);
/**
* Transforms data from one shape to another using a declarative schema.
* The schema defines how properties should be mapped or transformed from
* the source object to create the target object.
*
* @typeParam From - The type of the source object
* @typeParam To - The type of the target object
* @typeParam TExtra - The type of any extra context data (defaults to Extra)
*
* @param schema - Array of transformation rules defining the mapping between source and target
* @param entity - Source object to transform
* @param extra - Optional additional context data available to transformation functions
*
* @returns A new object matching the target type specification
*
* @example
* Basic property mapping:
* ```typescript
* import { mutate } from 'mutant';
*
* const schema = [
* { from: 'firstName', to: 'givenName' },
* { from: 'lastName', to: 'familyName' }
* ];
*
* const result = mutate(schema, {
* firstName: 'John',
* lastName: 'Doe'
* });
* // Result: { givenName: 'John', familyName: 'Doe' }
* ```
*
* @example
* Using transformation functions:
* ```typescript
* import { mutate } from 'mutant';
*
* const schema = [
* {
* to: 'fullName',
* fn: ({ entity }) => `${entity.firstName} ${entity.lastName}`
* },
* {
* from: 'age',
* to: 'isAdult',
* fn: ({ entity }) => entity.age >= 18
* }
* ];
*
* const result = mutate(schema, {
* firstName: 'John',
* lastName: 'Doe',
* age: 25
* });
* // Result: { fullName: 'John Doe', isAdult: true }
* ```
*
* @example
* Using extra context:
* ```typescript
* import { mutate } from 'mutant';
*
* const schema = [
* {
* to: 'greeting',
* fn: ({ entity, extra }) =>
* `${extra.greeting}, ${entity.firstName}!`
* }
* ];
*
* const result = mutate(schema,
* { firstName: 'John' },
* { greeting: 'Hello' }
* );
* // Result: { greeting: 'Hello, John!' }
* ```
*
* @throws {TypeError} If schema or entity are null/undefined
* @throws {Error} If schema contains invalid transformation rules
*/
const mutate = (schema, entity, extra) => schema.reduce((acc, rule) => {
const value = 'fn' in rule
? rule.fn({ entity, from: 'from' in rule ? rule.from : undefined, extra })
: entity[rule.from];
return {
...acc,
[rule.to]: value
};
}, {});
exports.mutate = mutate;
Loading

0 comments on commit 6998fba

Please sign in to comment.