Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **Dynamic Mapping Expansions**: `map()` and `reverseMap()` now accept an optional `options`
parameter. Setting `{ dynamicMapping: true }` causes any source fields that are not present in
the field mapping to be copied through to the result unchanged. This is useful when working with
APIs or datasets where the schema is only partially known.
- New exported type `MapOptions` that describes the options object accepted by `map()` and
`reverseMap()`.
- `devDependencies` added to `package.json` (`typescript`, `vitest`, `eslint`,
`@typescript-eslint/eslint-plugin`, `@typescript-eslint/parser`) so the project builds and tests
without requiring an external monorepo root.

### Fixed
- `tsconfig.json` no longer extends `../../tsconfig.base.json` (a file that does not exist in the
standalone repository). All required compiler options are now inlined directly.

## [0.1.1] - 2026-03-02

### Fixed
Expand Down
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Transform data between different shapes (API ↔ Domain) without writing repetit
- [Why?](#why)
- [Quick Start](#quick-start)
- [Key Features](#key-features)
- [Dynamic Mapping](#dynamic-mapping)
- [Use Cases](#use-cases)
- [API Reference](#api-reference)
- [Important Notes](#important-notes)
Expand Down Expand Up @@ -88,9 +89,50 @@ const api = mapper.reverseMap({ isEnterprise: false, commerceType: 'B2C' });
- **Zero Duplication**: Define field mappings once, get TypeScript types automatically
- **Full Type Safety**: TypeScript infers mapped types from your field mappings
- **Bidirectional**: Map from external → internal and internal → external
- **Dynamic Mapping**: Optionally pass through unmapped fields for partially-known schemas
- **Optional Fields**: Handles optional values correctly in both directions
- **Zero Dependencies**: No runtime dependencies

## Dynamic Mapping

When working with APIs or datasets where the schema is only partially known, you can use
`{ dynamicMapping: true }` to copy unmapped fields through as-is rather than dropping them.

```typescript
const fieldMapping = {
custom_a: 'isEnterprise',
} as const;

class UserMapper extends MappedServiceBase<ApiRow, typeof fieldMapping> {
protected fieldMapping = fieldMapping;
}

const mapper = new UserMapper();

const data = {
custom_a: true,
other_field: 'dynamic_value', // not in the mapping
};

// Without dynamicMapping (default) — unmapped fields are dropped
mapper.map(data);
// { isEnterprise: true }

// With dynamicMapping — unmapped fields are passed through
mapper.map(data, { dynamicMapping: true });
// { isEnterprise: true, other_field: 'dynamic_value' }
```

The same option is available on `reverseMap()`:

```typescript
mapper.reverseMap({ isEnterprise: true, extra_info: 42 }, { dynamicMapping: true });
// { custom_a: true, extra_info: 42 }
```

> **Note:** The `dynamicMapping` option is `false` by default. Enabling it is an explicit,
> opt-in behaviour and introduces no breaking changes to existing code.

## API Reference

### `MappedServiceBase<TSource, TMapping>`
Expand All @@ -104,8 +146,16 @@ Abstract base class for creating type-safe field mappers.

**Methods:**

- `map(source: Partial<TSource>): MappedType<TSource, TMapping>` - Transform external to internal
- `reverseMap(target: Partial<MappedType<TSource, TMapping>>): Partial<TSource>` - Transform internal to external
- `map(source: Partial<TSource>, options?: MapOptions): MappedType<TSource, TMapping>` - Transform external to internal
- `reverseMap(target: Partial<MappedType<TSource, TMapping>>, options?: MapOptions): Partial<TSource>` - Transform internal to external

### `MapOptions`

Options object accepted by `map()` and `reverseMap()`.

| Property | Type | Default | Description |
|---|---|---|---|
| `dynamicMapping` | `boolean` | `false` | When `true`, fields not present in the mapping are copied through to the result unchanged. |

### `MappedType<TSource, M>`

Expand Down
Loading