npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

strict-type-mapper

v1.0.15

Published

StrictTypeMapper is a Mapper / AutoMapper library that uses advanced **Compile-time type checking** to enforce strict type compatibility. The checks are more strict than standard TypeScript constraints which helps catching runtime errors.

Downloads

28

Readme

Strict Type Mapper

StrictTypeMapper is a Mapper / AutoMapper library that uses advanced Compile-time type checking to enforce strict type compatibility. The checks are more strict than standard TypeScript constraints which helps catching runtime errors.

This library has been originally designed to map domain types to DB types as a part of domain-repository npm package, but recently it has been published as a separate library.

NOTE: if you have an error in mapping, it probably means that you are trying to map Optional type to non-Optional type or vice versa. Unfortunately, TypesScript does not provide meaningful error messages in such cases so you have to believe that library is right and your code is wrong :-)

If you want to see some TypeScript hacks, please check the following files:

1. Installation

npm install strict-type-mapper

2 Getting started

2.1 Strict type mapping

Define a strict mapping by creating an object of type Mapping<Source, Target>.

type Source = {
  name: string;
  age: number;
};

type Target = {
  name: string;
  age: number;
};

const mapping: Mapping<Source, Target> = {
  name: 'name',
  age: 'age'
};

When you make mistake in types, the compiler will scream:

type Source = {
  name?: string;
  age: number;
};

type Target = {
  name: string; //ERROR! this needs to be optional to be mappable to sourceName
  age: number;
};

const mapping: Mapping<Source, Target> = {
  // @ts-expect-error
  name: 'name',
  age: 'age'
};

2.2 Initialize StrictTypeMapper

Initialize StrictTypeMapper class, by providing explicit Source and Target type parameters.

const typeMapper = new StrictTypeMapper<Source, Target>(mapping);

2.3 Mapping objects

Use map() function to map Source to Target type.

const source: Source = {
  name: 'Jack',
  age: 21
};

const target = typeMapper.map(source);

expect(target.name).toEqual('Jack');
expect(target.age).toBe(21);

Use mapReverse() function to map Target to Source type.

3. Custom transformations

You can also provide transformation functions to map properties and reverse transformations using MapTo() utility functions.

Remember to exclude undefined checks (even if type is optional), because this is handled automatically by the library.

Null is treated as a valid value, so you need to handle it explicitly.

type A = { a: number; b: string; c: boolean };
type B = { a: number; b: string; c: string };

const mapping: Mapping<A, B> = {
  a: 'a',
  b: MapTo.Property(
    'b',
    (sourceB: string) => sourceB.toUpperCase(),
    (targetB: string) => targetB.toLowerCase()
  ),
  c: MapTo.Property(
    'c',
    (sourceC: boolean) => (sourceC ? 'true' : 'false'),
    (targetC: string) => targetC === 'true'
  )
};

const typeMapper = new StrictTypeMapper<A, B>(mapping);

const target = typeMapper.map({
  a: 1,
  b: 'hello',
  c: true
});

expect(target.a).toBe(1);
expect(target.b).toEqual('HELLO');
expect(target.c).toBe('true');

const source = typeMapper.mapReverse(target);

expect(source.a).toBe(1);
expect(source.b).toEqual('hello');
expect(source.c).toBe(true);

4. Complex and nested type mapping

Use it when you want to map complex and nested types. Example type:

export type FriendObject = {
  name: string;
  age: number;
  level?: number;
};

export type AdditionalObject = {
  serialNumber: string;
  index?: number;
};

export type FeaturesObject = {
  color: string;
  level?: number;
  additional?: AdditionalObject;
};

export type AnimalObject = {
  name: string;
  name2: string;
  name3: string;
  nameNullable?: string;
  age: number;
  ageNullable?: number | null;
  friendIDs: number[];
  friendIDsNullable?: number[];
  friends: FriendObject[];
  friendsNullable?: FriendObject[];
  features: FeaturesObject;
  featuresNullable?: FeaturesObject;
};

Example complex mapping:

export const additionalMapping: Mapping<AdditionalObject, AdditionalObject> = {
  serialNumber: MapTo.Property(
    'serialNumber',
    (sourceSN: string) => sourceSN + '_new',
    (targetSN: string) => targetSN.replace('_new', '')
  ),
  index: 'index'
};

export const friendMapping: Mapping<FriendObject, FriendObject> = {
  age: MapTo.Property(
    'age',
    (sourceAge: number) => sourceAge + 1,
    (targetAge: number) => targetAge - 1
  ),
  name: 'name',
  level: 'level'
};

export const featuresMapping: Mapping<FeaturesObject, FeaturesObject> = {
  color: MapTo.Property(
    'color',
    (sourceColor: string) => sourceColor + '_changed',
    (targetColor: string) => targetColor.replace('_changed', '')
  ),
  level: MapTo.Property(
    'level',
    (sourceLevel: number) => sourceLevel + 3,
    (targetLevel: number) => targetLevel - 3
  ),
  additional: MapTo.NestedObject('additional', additionalMapping)
};

export const complexMapping: Mapping<AnimalObject, MappedAnimalObject> = {
  name: 'name2',
  name2: 'name3',
  name3: 'name',
  nameNullable: MapTo.Property(
    'nameNullable',
    (sourceName: string) => sourceName.toUpperCase(),
    (targetName: string) => targetName.toLowerCase()
  ),
  age: MapTo.Property(
    'age',
    (sourceAge: number) => sourceAge + 1,
    (targetAge: number) => targetAge - 1
  ),
  ageNullable: MapTo.Property(
    'age_nullable',
    (sourceAge: number | null): number | null => sourceAge || 0,
    (targetAge: number | null): number | null => targetAge || 0
  ),
  friendIDsNullable: MapTo.Array(
    'friendIDsNullable',
    (sourceFriendId: number) => -sourceFriendId,
    (targetFriendId: number) => -targetFriendId
  ),
  friends: MapTo.ObjectArray('friends', friendMapping),
  friendsNullable: MapTo.ObjectArray('friends_nullable', friendMapping),
  friendIDs: 'friendIDs',
  features: MapTo.NestedObject('features', featuresMapping),
  featuresNullable: MapTo.NestedObject('features_nullable', featuresMapping)
};

5. Partial mapping

In principle you should map all properties, to make sure the types are correct. If you want to make some properties optional, add ? to their type definition (for both Source and Target types).

In rare cases when you want to map a subtype of Source, use Pick type or create a dedicated subset type, as shown here:

export type PartialAnimal = Pick<AnimalObject, 'name' | 'name2' | 'name3' | 'nameNullable' | 'age'>;

export const partialMapping: Mapping<PartialAnimal, MappedAnimalObject> = {
  name: 'name2',
  name2: 'name3',
  name3: 'name',
  nameNullable: MapTo.Property(
    'nameNullable',
    (sourceName: string) => sourceName.toUpperCase(),
    (targetName: string) => targetName.toLowerCase()
  ),
  age: MapTo.Property(
    'age',
    (sourceAge: number) => sourceAge + 1,
    (targetAge: number) => targetAge - 1
  )
};

6. Optional mapping

In rare cases when you want to make all mappings optional, set the 3rd type parameter of Mapping and StrictTypeMapper to false.

export const optionalMapping: Mapping<AnimalObject, MappedAnimalObject, false> = {
  name: 'name2',
  name2: 'name3',
  name3: 'name',
  nameNullable: MapTo.Property(
    'nameNullable',
    (sourceName: string) => sourceName.toUpperCase(),
    (targetName: string) => targetName.toLowerCase()
  ),
  age: MapTo.Property(
    'age',
    (sourceAge: number) => sourceAge + 1,
    (targetAge: number) => targetAge - 1
  )
};

The false type parameter will change the output types to Partial<Source> and Partial<Target>.

7. Mapping details:

Sometimes you may want to get all mapping details, for example to check if all properties are mapped correctly.

To get all mapping details use getCompiledMapping method.

const sourceKeys = partialMapper.getCompiledMapping().sourceKeys;

expect(sourceKeys).toContain('name');
expect(sourceKeys).toContain('name2');
expect(sourceKeys).toContain('name3');

All details:

export class CompiledMapping {
  public readonly sourceKeys: string[];
  public readonly nestedSourceKeys: string[];
  public readonly targetKeyToSourceKeyMap: Record<string, string>;
  public readonly sourceKeyToTargetKeyMap: Record<string, string>;
  public readonly targetKeyToFuncMap: Record<string, (i: unknown) => unknown>;
  public readonly sourceKeyToFuncMap: Record<string, (i: unknown) => unknown>;
  public readonly targetElementKeyToFuncMap: Record<string, (i: unknown) => unknown>;
  public readonly sourceElementKeyToFuncMap: Record<string, (i: unknown) => unknown>;
  public readonly targetKeyToNestedMapping: Record<string, CompiledMapping>;
  public readonly sourceKeyToNestedMapping: Record<string, CompiledMapping>;