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

@stackoverfloweth/mapper

v0.0.4

Published

Simple and versatile mapping utility for Typescript.

Downloads

4

Readme

mapper

Basic Setup

Each mapper relies an an underlying set of Profile objects to define what types are supported and how to map between them. Mappers are created with createMapper function, which takes Profile[] as it's only argument.

import { createMapper } from '@stackoverfloweth/mapper'

const mapper = createMapper(profiles)

mapper.map('source-key', source, 'destination-key')

Profiles

Each profile defines a value for sourceKey and destinationKey. These keys must extend string and should be unique, beyond that the choice is irrelevant to the function of the mapper.

Here are a couple simple examples of Profile objects

export const numberToString = {
  sourceKey: 'number',
  destinationKey: 'string',
  map: (source) => {
    return source.toString()
  },
} as const satisfies Profile

export const numberToDate = {
  sourceKey: 'number',
  destinationKey: 'Date',
  map: (source) => {
    return new Date(source)
  },
} as const satisfies Profile

The mapper will use the keys you define to provide type safety when calling map.

mapper.map('number', 123, 'string') // "123"
mapper.map('number', 123, 'Date')   // Wed Dec 31 1969...
mapper.map('number', 123, 'potato') // ERROR TS:2345 Argument of type '"potato"' is not assignable to parameter of type '"string" | "Date"'

Anytime mapper.map is called with source and/or destination keys that are not registered by a profile, it will throw the following error.

'Mapping profile not found'

Loading profiles automatically

This library provides a useful method for automatically loading profiles. If you store all of your profiles in the same folder with a simple barrel file.

└── src
   ├── models
   └── maps
      ├── primitives.ts
      ├── foo.ts
      ├── bar.ts
      └── index.ts
const profiles = await loadProfiles(() => import('src/maps'))

const mapper = createMapper(profiles)

Mapping an array

Because TSource and TDestination are not constrained, you can always define a profile that expects an array.

export const numbersArrayToNumbersSet: Profile<'array', unknown[], 'Set', Set<unknown>> = {
  sourceKey: 'array',
  destinationKey: 'Set',
  map: (source) => {
    return new Set(source)
  },
}

However, if your goal is use the same mapping profile over an array of sources you can use either

const mapped = sources.map(source => mapper.map('source-key', source, 'destination-key'))

or the mapper provides a simpler method mapMany, which takes an array of TSource and returns an array TDestination.

const mapped = mapper.mapMany('source-key', sources, 'destination-key')

Nesting profiles

Sometimes your business logic for mapping from TSource to TDestination might benefit from nesting profiles inside of other profiles. For example, if you have the following models

// src/models/order.ts

export type Order = {
  orderId: string,
  total: number,
  items: Item[],
}
// src/models/item.ts

export type Item = {
  itemId: string,
  title: string,
  description: string,
}

and you need to map from api models

// src/models/api/orderResponse.ts

export type OrderResponse = {
  _id: ObjectId,
  totalInPennies: number,
  items?: ItemResponse[],
}
// src/models/api/itemResponse.ts

export type ItemResponse = {
  _id: ObjectId,
  title: string,
  description?: string,
}

There are a couple opportunities to use the mapper from within the order profile. Both the ObjectId from mongodb and Item mapping logic is likely already encapsulated by another profile. This is where the mapper argument provided to the profile map method comes in handy.

export const orderResponseToOrder: Profile<'OrderResponse', OrderResponse, 'Order', Order> = {
  sourceKey: 'OrderResponse',
  destinationKey: 'Order',
  map: (source, mapper) => {
    return {
      orderId: mapper.map('ObjectId', source._id, 'string'),
      total: number,
      items: mapper.map('ItemResponse', source.items ?? [], 'Item'),
    }
  },
}

However, there's a catch. Because the type safety is controlled by your application supplying the profiles to createMapper, the library itself cannot have an accurate type for mapper here. The type provided by default is AnyMapper, which unlike the mapper you get back from createMapper will have no constraints on keys or types.

The solution to this is to take advantage of ts duck typing by defining your own type for MapFunction that has narrowed types but still satisfies the requirement of Profile.map.


export const mapper = createMapper(profiles)
export type Mapper = typeof mapper

export type MapFunction<TSource, TDestination> = (source: TSource, mapper: Mapper) => TDestination

Now you can rewrite your order profile with type safety

import { MapFunction } from 'src/services'

const map: MapFunction<OrderResponse, Order> = (source, mapper) => {
  return {
    orderId: mapper.map('ObjectId', source._id, 'string'),
    total: number,
    items: mapper.map('ItemResponse', source.items ?? [], 'Item'),
  }
}

export const orderResponseToOrder: Profile<'OrderResponse', OrderResponse, 'Order', Order> = {
  sourceKey: 'OrderResponse',
  destinationKey: 'Order',
  map,
}

Notes

  • What you chose to name the profile doesn't matter to the mapper. In these examples I used the pattern ${sourceKey}To${destinationKey} but this key is not currently used by loadProfiles() in any way.