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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@typesugar/mapper

v0.1.1

Published

🧊 Zero-cost compile-time object mapping and transformation

Downloads

33

Readme

@typesugar/mapper

Zero-cost compile-time object mapping and transformation. Inspired by Scala's Chimney and .NET's AutoMapper, but fully integrated into TypeScript with zero runtime overhead.

The Problem

Every application has DTO/entity boundaries. Converting between database records, API responses, and domain models requires tedious, error-prone boilerplate:

// Without @typesugar/mapper — manual boilerplate everywhere
function toUserDTO(user: User): UserDTO {
  return {
    firstName: user.first_name,
    lastName: user.last_name,
    age: user.age,
    role: "user",
    fullName: `${user.first_name} ${user.last_name}`,
  };
}

// Repeated for every entity × every boundary
function toProductDTO(product: Product): ProductDTO {
  /* ... */
}
function toOrderDTO(order: Order): OrderDTO {
  /* ... */
}
function toCustomerDTO(customer: Customer): CustomerDTO {
  /* ... */
}

This boilerplate is:

  • Error-prone: Field name typos compile but produce runtime bugs
  • Verbose: Every mapping requires a dedicated function
  • Unmaintainable: Adding fields requires updating multiple locations

The Solution

transformInto<From, To>() is a compile-time macro that generates the mapping code for you:

import { transformInto } from "@typesugar/mapper";

// Compiles to direct property assignments — zero runtime overhead
const dto = transformInto<User, UserDTO>(user, {
  rename: { firstName: "first_name", lastName: "last_name" },
  const: { role: "user" },
  compute: { fullName: (u) => `${u.first_name} ${u.last_name}` },
});

The compiler:

  1. Validates that all target fields are mapped (build fails if not)
  2. Transforms the call into a plain object literal
  3. Inlines computed fields directly

Result: Type-safe, maintainable mapping with zero runtime cost.

Installation

npm install @typesugar/mapper
# or
pnpm add @typesugar/mapper

Requires the typesugar transformer to be configured in your build tool.

Quick Start

import { transformInto } from "@typesugar/mapper";

interface User {
  first_name: string;
  last_name: string;
  age: number;
}

interface UserDTO {
  firstName: string;
  lastName: string;
  age: number;
  role: string;
}

const user: User = { first_name: "John", last_name: "Doe", age: 30 };

const dto = transformInto<User, UserDTO>(user, {
  rename: {
    firstName: "first_name",
    lastName: "last_name",
  },
  const: {
    role: "user",
  },
});

// dto is { firstName: "John", lastName: "Doe", age: 30, role: "user" }

Features

Basic Mapping — Same Field Names

When source and target types have matching field names, no configuration is needed:

interface UserEntity {
  name: string;
  email: string;
  age: number;
}

interface UserDTO {
  name: string;
  email: string;
  age: number;
}

const entity: UserEntity = { name: "Alice", email: "[email protected]", age: 30 };
const dto = transformInto<UserEntity, UserDTO>(entity);
// dto is { name: "Alice", email: "[email protected]", age: 30 }

Field Renaming — rename

Map fields with different names between source and target:

interface DbRecord {
  user_name: string;
  user_email: string;
  user_age: number;
}

interface ApiResponse {
  name: string;
  email: string;
  age: number;
}

const dbRow: DbRecord = { user_name: "Bob", user_email: "[email protected]", user_age: 25 };

const response = transformInto<DbRecord, ApiResponse>(dbRow, {
  rename: {
    name: "user_name", // target field: source field
    email: "user_email",
    age: "user_age",
  },
});
// response is { name: "Bob", email: "[email protected]", age: 25 }

The rename config uses target keys pointing to source field names.

Computed Fields — compute

Derive target field values from source data using functions:

interface FullName {
  firstName: string;
  lastName: string;
}

interface DisplayInfo {
  fullName: string;
  initials: string;
}

const name: FullName = { firstName: "Jane", lastName: "Doe" };

const display = transformInto<FullName, DisplayInfo>(name, {
  compute: {
    fullName: (src) => `${src.firstName} ${src.lastName}`,
    initials: (src) => `${src.firstName[0]}${src.lastName[0]}`,
  },
});
// display is { fullName: "Jane Doe", initials: "JD" }

Compute functions receive the entire source object and return the target field value.

Constant Values — const

Inject fixed values for target fields that don't exist in the source:

interface Input {
  value: number;
}

interface Output {
  value: number;
  source: string;
  version: number;
}

const input: Input = { value: 42 };

const output = transformInto<Input, Output>(input, {
  const: {
    source: "manual",
    version: 1,
  },
});
// output is { value: 42, source: "manual", version: 1 }

Ignoring Fields — ignore

Skip source fields that shouldn't be mapped, or target fields that should be omitted:

interface Verbose {
  id: number;
  name: string;
  internalCode: string;
  debugInfo: string;
}

interface Brief {
  id: number;
  name: string;
}

const verbose: Verbose = { id: 1, name: "Test", internalCode: "X", debugInfo: "..." };

const brief = transformInto<Verbose, Brief>(verbose, {
  ignore: {
    source: ["internalCode", "debugInfo"], // Don't complain about unmapped source fields
  },
});
// brief is { id: 1, name: "Test" }

Use ignore.target to exclude target fields from the output entirely:

const partial = transformInto<Source, Target>(src, {
  ignore: {
    target: ["optionalField"], // Omit this field from output
  },
});

Combined Configuration

All config options can be used together:

interface OrderRow {
  order_id: number;
  total_cents: number;
  customer_name: string;
}

interface OrderSummary {
  id: number;
  totalFormatted: string;
  customer: string;
  currency: string;
}

const row: OrderRow = { order_id: 1001, total_cents: 4999, customer_name: "Charlie" };

const summary = transformInto<OrderRow, OrderSummary>(row, {
  rename: {
    id: "order_id",
    customer: "customer_name",
  },
  compute: {
    totalFormatted: (src) => `$${(src.total_cents / 100).toFixed(2)}`,
  },
  const: {
    currency: "USD",
  },
});
// summary is { id: 1001, totalFormatted: "$49.99", customer: "Charlie", currency: "USD" }

Zero-Cost Guarantee

transformInto() is a compile-time macro. The function call is completely erased and replaced with a direct object literal.

Source code:

const dto = transformInto<User, UserDTO>(user, {
  rename: { firstName: "first_name" },
  const: { role: "user" },
});

Compiled output:

const dto = {
  firstName: user.first_name,
  age: user.age,
  role: "user",
};

No function calls, no runtime libraries, no overhead. The transformation is resolved entirely at compile time.

Complex Source Expressions

When the source is not a simple identifier (e.g., a function call), the macro wraps the output in an IIFE to avoid evaluating the source multiple times:

// Source code
const dto = transformInto<From, To>(getUser(), config);

// Compiled output (simplified)
const dto = (() => {
  const src = getUser();
  return { name: src.name, email: src.email };
})();

Type Safety

The TransformConfig<From, To> type enforces that:

  1. Rename keys are valid target field names
  2. Rename values are valid source field names
  3. Compute functions receive the correct source type and return the correct target field type
  4. Const values match the target field types
transformInto<User, UserDTO>(user, {
  rename: {
    firstName: "first_name",
    lastName: "typo_field", // ✗ Type error: "typo_field" not in keyof User
  },
  const: {
    role: 123, // ✗ Type error: number not assignable to string
  },
});

Build-Time Validation

If a target field cannot be mapped (no matching source field, no rename, no compute, no const), the build fails:

error: Cannot map field 'missingField': No matching field 'missingField' in source type and no constant/compute rule provided.

API Quick Reference

Function

| Export | Description | | ------------------------------------------ | -------------------------------------- | | transformInto<From, To>(source, config?) | Transform source object to target type |

TransformConfig Properties

| Property | Type | Description | | --------------- | -------------------------------------------- | ----------------------------------------------------- | | rename | { [K in keyof To]?: keyof From } | Map target fields to differently-named source fields | | compute | { [K in keyof To]?: (src: From) => To[K] } | Derive target fields from source data | | const | { [K in keyof To]?: To[K] } | Inject constant values for target fields | | ignore.source | (keyof From)[] | Source fields to ignore (no "unmapped field" warning) | | ignore.target | (keyof To)[] | Target fields to exclude from output |


Comparison with Alternatives

vs. Scala Chimney

Chimney is the inspiration for this library. Both provide:

  • Compile-time transformation validation
  • Automatic same-name field mapping
  • Field renaming, computed fields, constants

Differences:

  • Chimney uses Scala macros; @typesugar/mapper uses TypeScript's compiler API
  • Chimney supports nested transformations and collections automatically; @typesugar/mapper is currently flat-only (nested support planned)
  • Chimney has richer DSL (withFieldRenamed, withFieldComputed); @typesugar/mapper uses config objects

vs. AutoMapper (.NET)

AutoMapper popularized the mapping library pattern:

  • Convention-based mapping (flattening, naming conventions)
  • Profile-based configuration
  • Runtime reflection

Differences:

  • AutoMapper runs at runtime; @typesugar/mapper is compile-time only
  • AutoMapper uses reflection; @typesugar/mapper has zero runtime overhead
  • AutoMapper supports complex scenarios (before/after maps, value resolvers); @typesugar/mapper is simpler

vs. Manual Mapping

Manual mapping functions are:

  • ✗ Verbose and repetitive
  • ✗ Error-prone (typos, missing fields)
  • ✗ Hard to maintain across many entities

@typesugar/mapper provides:

  • ✓ Concise declaration
  • ✓ Compile-time validation
  • ✓ Zero runtime overhead (same as manual)

Roadmap

Features planned for future releases:

  • Nested object transformation: Recursively transform nested types
  • Collection mapping: Map arrays/lists of objects
  • Partial mapping: Create partial types from subsets
  • Bidirectional mapping: Generate inverse transformations
  • Validation integration: Combine with @typesugar/validate

License

MIT