JSPM

@d3vtool/automapper

1.2.2
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 18
    • Score
      100M100P100Q47675F
    • License MIT

    AutoMapper

    Package Exports

    • @d3vtool/automapper
    • @d3vtool/automapper/dist/cjs/index.js

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@d3vtool/automapper) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    ๐Ÿ”„ AutoMapper Utility

    AutoMapper is a lightweight TypeScript utility designed to automate the transformation of objects between DTOs and Entities. It simplifies mapping between classes with matching or custom field names, keeping data transformation code clean and maintainable.

    In addition to object mapping, it also supports decorator-based DTO validation at instantiation time, allowing you to enforce field-level rules such as @Required, @Email, @StringLength, and more. Validation errors throw a clear ValidationFailedError with the field name and message.

    Includes a JsonObject base class for easily converting class instances to and from plain object literals (JSON-compatible structures).


    Table of Contents


    ๐Ÿ“ฆ Installation

    Install via your preferred package manager:

    npm

    npm install @d3vtool/automapper

    yarn

    yarn add @d3vtool/automapper

    โœ… Testing

    To run the tests:

    npm run test

    โœจ Features

    • Automatic mapping of fields between objects with matching names.

    • Explicit mapping support for different field names using forMember().

    • Allows multiple mapping configurations (e.g., User to UserDto and UserDto to User).

    • JSON serialization and deserialization using the JsonObject base class.

    • DTO decorator-based validation โ€” enforce rules at instantiation time with built-in and custom validators.

    • Built-in decorators:

      • @Required() โ€“ enforce presence of a value.
      • @StringLength() โ€“ set minimum/maximum string length.
      • @Email() โ€“ validate RFC-compliant email addresses.
      • @Password() โ€“ validate password strength.
      • @Regex() โ€“ validate values against a regular expression.
      • @CustomFn() โ€“ run custom validation logic.
    • Automatic error handling via ValidationFailedError with .field and .message for failed validations.


    ๐Ÿงช Examples

    ๐Ÿ”น JsonObject Usage

    1. Convert JSON to Entity

    import { JsonObject } from "@d3vtool/automapper";
    
    class User extends JsonObject<User> {
      constructor(
        public id?: string,
        public name?: string,
        public email?: string,
        public password?: string
      ) {
        super();
      }
    }
    
    const user = new User();
    user.fromJson({
      id: "12",
      name: "Alice",
      email: "alice@mail.com",
      password: "secure123"
    });
    
    console.log(user.name); // "Alice"

    2. Convert Entity to JSON

    const user = new User("12", "Alice", "alice@mail.com", "secure123");
    const json = user.toJson();
    
    console.log(json);
    // { id: '12', name: 'Alice', email: 'alice@mail.com', password: 'secure123' }

    ๐Ÿ”น AutoMapper Usage

    1. Create autoMapper instance

    To create and reuse the AutoMapper instance:

    import { AutoMapper } from "@d3vtool/automapper";
    
    // Create one instance and reuse it across your application.
    export const autoMapper = new AutoMapper();

    2. Map with Matching Field Names (One Direction)

    import { autoMapper } from "./somefile";
    import { JsonObject } from "@d3vtool/automapper";
    
    class User extends JsonObject<User> {
      constructor(
        public id?: string,
        public name?: string,
        public email?: string,
        public password?: string
      ) {
        super();
      }
    }
    
    class UserDto extends JsonObject<UserDto> {
      constructor(
        public id?: string,
        public name?: string,
        public email?: string
      ) {
        super();
      }
    }
    
    const userToDtoMapper = autoMapper.map(User, UserDto);
    
    const user = new User("12", "Alice", "alice@mail.com", "secure123");
    const userDto = userToDtoMapper.map(user);
    
    console.log(userDto);
    // Output: UserDto { id: '12', name: 'Alice', email: 'alice@mail.com' }

    3. Custom Mapping with Different Field Names

    class User extends JsonObject<User> {
      constructor(
        public id?: string,
        public firstName?: string,
        public lastName?: string,
        public email?: string,
        public password?: string
      ) {
        super();
      }
    }
    
    class UserDto extends JsonObject<UserDto> {
      constructor(
        public id?: string,
        public fullName?: string,
        public email?: string
      ) {
        super();
      }
    }
    
    const userToDtoMapper = autoMapper.map(User, UserDto);
    
    // Custom mapping for `fullName` (combining `firstName` + `lastName`)
    userToDtoMapper.forMember("fullName", (user) => `${user.firstName} ${user.lastName}`);
    
    const user = new User("12", "Alice", "Smith", "alice@mail.com", "secure123");
    const userDto = userToDtoMapper.map(user);
    
    console.log(userDto);
    // Output: UserDto { id: '12', fullName: 'Alice Smith', email: 'alice@mail.com' }

    4. Explicit Reverse Mapping (UserDto to User)

    To map in the reverse direction, you must explicitly create a new mapping:

    const dtoToUserMapper = autoMapper.map(UserDto, User);
    
    // Assuming userDto is an instance of UserDto and fields are same of entity and dto.
    const userFromDto = dtoToUserMapper.map(userDto);
    
    // If fields aren't same then:
    dtoToUserMapper
        .forMember("firstName", (dto) => dto.fullName?.split(" ")[0])
        .forMember("lastName", (dto) => dto.fullName?.split(" ")[1]);
    
    const userFromDto = dtoToUserMapper.map(userDto);
    
    console.log(userFromDto);
    // Output: User { id: '12', firstName: 'Alice', lastName: 'Smith', email: 'alice@mail.com', password: 'secure123' }

    DTO Validation Decorators Usage

    The @DTO decorator, in combination with field-level validation decorators, allows you to enforce rules at instantiation time. If validation fails, ValidationFailedError is thrown with the invalid field and a descriptive message.

    Note: To use decorators in TypeScript, you must enable the experimental feature in your tsconfig.json:

    {
      "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
      }
    }

    All decorators (except StringLength) accept an optional final parameter:

    errorMsg?: string // Custom error message for this validation rule

    1. @Required()

    Marks a field as mandatory. Missing or empty values will cause validation to fail.

    @DTO
    class User extends JsonObject<User> {
      constructor(
        @Required("Name is required")
        public name?: string
      ) {
        super();
      }
    }
    
    // โœ… Passes
    new User("Alice");
    
    // โŒ Fails
    try {
      new User();
    } catch (err) {
      console.log(err instanceof ValidationFailedError); // true
      console.log(err.field); // "name"
      console.log(err.message); // "Name is required"
    }

    2. @StringLength(minLength, maxLength?, errorMsg?: StringLengthParamError)

    Enforces string length limits. You can provide different error messages for minLength and maxLength failures.

    @DTO
    class User extends JsonObject<User> {
      constructor(
        @StringLength(5, 10, {
          minLength: "Name must be at least 5 characters",
          maxLength: "Name must be no more than 10 characters"
        })
        public name?: string
      ) {
        super();
      }
    }
    
    // โœ… Passes
    new User("Alice");
    
    // โŒ Too short
    try {
      new User("Al");
    } catch (err) {
      console.log(err.field); // "name"
      console.log(err.message); // "Name must be at least 5 characters"
    }
    
    // โŒ Too long
    try {
      new User("AlexandriaTheGreat");
    } catch (err) {
      console.log(err.field); // "name"
      console.log(err.message); // "Name must be no more than 10 characters"
    }

    3. @Email()

    Validates that the field contains a valid RFC-compliant email address.

    @DTO
    class User extends JsonObject<User> {
      constructor(
        @Email("Invalid email address")
        public email?: string
      ) {
        super();
      }
    }
    
    // โœ… Passes
    new User("ok.user+tag@example.co.uk");
    
    // โŒ Invalid
    try {
      new User("invalid-email");
    } catch (err) {
      console.log(err.field); // "email"
      console.log(err.message); // "Invalid email address"
    }

    4. @Password()

    Validates password strength (implementation-dependent, e.g., min length, uppercase, number, symbol).

    @DTO
    class User extends JsonObject<User> {
      constructor(
        @Password("Password is too weak")
        public password?: string
      ) {
        super();
      }
    }
    
    // โœ… Passes
    new User("Aa1!aaaa");
    
    // โŒ Too weak
    try {
      new User("123");
    } catch (err) {
      console.log(err.field); // "password"
      console.log(err.message); // "Password is too weak"
    }

    5. @Regex(pattern: RegExp, errorMsg?: string)

    Validates that the string matches a given pattern. Example: Austrian phone numbers starting with +43.

    @DTO
    class User extends JsonObject<User> {
      constructor(
        @Regex(/^\+43\s?(\d[\s-]?){4,14}$/, "Invalid Austrian phone number")
        public phone?: string
      ) {
        super();
      }
    }
    
    // โœ… Passes
    new User("+43 123-456-789");
    
    // โŒ Invalid
    try {
      new User("0043 123456");
    } catch (err) {
      console.log(err.field); // "phone"
      console.log(err.message); // "Invalid Austrian phone number"
    }

    6. @CustomFn(validatorFn: (value: unknown) => true | string, errorMsg?: string)

    Runs a custom validation function. If it returns true, validation passes. If it returns a string, that string is used as the error message.

    /**
     * Custom field validation function.
     *
     * Parameters:
     * - `value`: The data for the current field being validated.
     * - `body` : The entire payload passed to the constructor.
     *
     * Notes:
     * - `body` can be used to perform cross-field validations and more.
     */
    export type CustomFn<T = unknown> = (value: T, body?: unknown) => boolean | ErrorString;
    @DTO
    class User extends JsonObject<User> {
      constructor(
        @CustomFn((value) => {
          if (typeof value !== "string") return "Date must be a string";
          const parts = value.split("/");
          if (parts.length !== 3) return "Invalid date format";
    
          const [dd, mm, yyyy] = parts.map(Number);
          const daysInMonth = new Date(yyyy, mm, 0).getDate();
    
          if (yyyy < 1900 || yyyy > new Date().getFullYear()) return "Invalid year";
          if (mm < 1 || mm > 12) return "Invalid month";
          if (dd < 1 || dd > daysInMonth) return "Invalid day";
    
          return true;
        })
        public birthDate?: string
      ) {
        super();
      }
    }
    
    // โœ… Passes
    new User("01/01/2000");
    
    // โŒ Invalid
    try {
      new User("32/01/2000");
    } catch (err) {
      console.log(err.field); // "birthDate"
      console.log(err.message); // "Invalid day"
    }
    // Referencing example 
    @DTO
    class User extends JsonObject<User> {
        constructor(
            @Required()
            @StringLength(4)
            public password?: string,
    
            @CustomFn((value: unknown, body?: unknown) => {
    
                const userBody = body as any;
                const confirmPassword = value as string;
    
                if (typeof confirmPassword !== 'string') {
                    return "Invalid confirm password field data.";
                }
    
                if (userBody?.password !== confirmPassword) {
                    return "Confirm Password and Password do not match.";
                }
    
                return true;
            })
            public confirmPassword?: string
        ) {
            super();
        }
    }
    
    new User("password", "password"); // โœ… Passes
    
    try {
    
      new User("password", "differentPassword"); // โŒ will fail.
    
    } catch(err: unknown) {
      if(err instanceof ValidationFailedError) {
        console.log(err.field, err.message);
      }
    }
    
    
    /**
     * Note:
     *  - Always wrap your constructor with try-catch
    */

    Error Types

    1. ValidationFailedError Thrown when a validation rule fails. Has:

      • field: the name of the invalid field
      • message: the error message
    2. Native Error Also thrown when a value does not meet validator constraints.

      • For example, in StringLength(min, max), if max < min, an error will be thrown with a descriptive message.

    This package is open-source and licensed under the MIT License.