Skip to content

LuxciumProject/mapping-tools

Repository files navigation

Mapping Tools

GitHub issues GitHub followers GitHub forks GitHub Repo stars GitHub watchers

npm type definitions Latest Version npm GitHub

Version Badge

Version Badge

Mapping Tools is a powerful package for mapping over lists and iterables in JavaScript and TypeScript.

It is designed to be user-friendly and easy to use, with clear documentation and examples, and is suitable for use in a wide range of applications, including data processing and validation.

The package provides a set of utility functions for working with collections of data, allowing you to apply transformations or validations to each item in a collection, either in serial or in parallel. These functions can generate new collections or iterators, as well as async iterators, based on the results. Mapping Tools also offers advanced error handling and support for asynchronous code.

Table of Contents

  1. Table of Contents
  2. Installation
  3. Usage Overview
  4. Quick Start
  5. Main Functions
  6. Delegates Functions
  7. Base Types
  8. Aliases Types

Installation

To install Mapping Tools, you can use npm, yarn, or pnpm:

# npm
npm install mapping-tools
# yarn
yarn add mapping-tools
# pnpm
pnpm add mapping-tools

Usage Overview

The package includes 5 main functions:

The mapping-tools package is incredibly flexible, able to handle both promises and non-promises as input. This means you can use it in any context, no matter what type of data you're working with.

Error handling is a top priority in the mapping-tools package. No matter what goes wrong, you can trust that the code will always provide meaningful output. This makes it a resilient choice for performing transformations on your data.

The output of the mapping-tools package is based on the Settled<TVal> type, wwhich allows users to differentiate between successful and unsuccessful results. This makes it easy to understand and work with the results of your transformations.

Overall, the mapping-tools package is a reliable choice for performing transformations on collections of data.

  1. awaitedMapping, is based on Promise.all($)
  2. parallelMapping, is based on Array.prototype.map($)
  3. serialMapping, is based on a forOf loop
  4. generateMapping, is based on the Generator protocol
  5. generateMappingAsync, is based on the AsyncGenerator protocol

These functions all take a collection of items as their main input, along with 4 delegate functions: transformFn, lookupFn, validateFn and errLookupFn.

The transformFn is a delegate function that is applied to each item in the collection. It is used to transform the item into a new value.

The lookupFn and validateFn are also delegate functions that are applied to each item in the collection. They can be used to perform additional lookup or validation operations on the transformed items.

The errLookupFn is a delegate function that is used to handle any errors that may occur during the processing of the collection or during previous steps of processing.

It is important to note that all four of these delegate functions are optional. If they are not provided, there will be no transformation or validation of the values.

Quick Start

To get started with Mapping Tools, you'll first need to import the library in your code:

const mappingTools = require('mapping-tools');
// or
import * as mappingTools from 'mapping-tools';

Then, you can use the various functions provided by the library to generate, transform, and iterate over maps, as well as to perform asynchronous map generation.

import { awaitedMapping, helpers } from 'mapping-tools';

const { extractFulfilledValues, extractSettledValues } = helpers;

async function main() {
  const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
  const mappedArray = await awaitedMapping(array, async element => {
    // Async operation on each element
    if (element % 4 === 2) {
      throw new Error('Error');
    }
    return element * 2;
  });

Using extractFulfilledValues will return only fulfilledValues changing the length of the array and the position of the elements if necessary

const fulfilledValues = extractFulfilledValues(mappedArray);
console.log('fulfilledValues :>> ', fulfilledValues);
console.log('fulfilledValues.length :>> ', fulfilledValues.length);
  • output:
/*
  fulfilledValues :>>  [
    2,  6,  8, 10, 14,
    16, 18, 22, 24
  ]
  fulfilledValues.length :>>  9
*/

Using extractFulfilledValues will return only settledValues (for which the SettledLeft has no value and will be returning NULL_SYMBOL instead) keeping the length of the array and the position of its elements.

  const settledValues = extractSettledValues(mappedArray);
  console.log('settledValues :>> ', settledValues);
  console.log('settledValues.length :>> ', settledValues.length);
  }
main();
  • output:
/*
  settledValues :>>  [
    2,  Symbol(null),
    6,  8,
    10, Symbol(null),
    14, 16,
    18, Symbol(null),
    22, 24
  ]
  settledValues.length :>>  12
*/

Main Functions

The project currently have 5 main flavours for its core functions they have complex signatures taht are easy to understand, and they can be grouped in diferrent manner:

  1. Functions that can accept either Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>> or PromiseLike<Iterable<Base<T>>> and which return promises that resolve to arrays: serialMapping, awaitedMapping, and generateMappingAsync returns
  2. Functions that can accept only Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>> and return arrays of promises or generators: parallelMapping returns, generateMapping returns

parallelMapping

parallelMapping(collection, TransformFn, LookupFn, ValidateFn, ErrLookupFn): Array<Promise<Settled<R>>>

Applies the provided callback functions to each item in the collection in parallel, and returns an array of promises that resolve to the transformed and validated items, represented as Settled<R> objects.

  • Based on an Array.prototype.map($)

  • Takes as its main input: Iterable<Base<T> | PromiseLike<Base<T>>> only

  • Returns: Array<Promise<Settled<R>>>

  • Function signature:

export type parallelMapping<T, R>(
  collection: Collection<T>,
  transformFn: TransformFn<T, R> | null = async value => value as any as R,
  lookupFn: LookupFn<T, R> | null = v => void v,
  validateFn: ValidateFn<T, R> | null = async v => void v,
  errLookupFn: ErrLookupFn | null = v => void v
): Promise<Settled<R>>[];

serialMapping

serialMapping(collection, TransformFn, LookupFn, ValidateFn, ErrLookupFn): Promise<Array<Settled<R>>>

Applies the provided callback functions to each item in the collection in series, and returns a promise that resolves to an array of the transformed and validated items, represented as Settled<R> objects.

  • Based on forOf loop

  • Takes as its main input: Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>> or PromiseLike<Iterable<Base<T>>>

  • Returns: Promise<Array<Settled<R>>>

  • Function signature:

export async function serialMapping<T, R>(
  collection: DeferredCollection<T>,
  transformFn: TransformFn<T, R> | null = async value => value as any as R,
  lookupFn: LookupFn<T, R> | null = v => void v,
  validateFn: ValidateFn<T, R> | null = async v => void v,
  errLookupFn: ErrLookupFn | null = v => void v
): Promise<Settled<R>[]>;

awaitedMapping

awaitedMapping(collection, TransformFn, LookupFn, ValidateFn, ErrLookupFn): Promise<Array<Settled<R>>>

Applies the provided callback functions to each item in the collection, and returns a promise that resolves to an array of the transformed and validated items, represented as Settled<R> objects.

  • Based on Promise.all($)

  • Takes as its main input: Iterable<Base<T> | PromiseLike<Base<T>>> or PromiseLike<Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>>>

  • Returns: Promise<Array<Settled<R>>>

  • Function signature:

export async function awaitedMapping<T, R>(
  collection: DeferredCollection<T>,
  transformFn: TransformFn<T, R> | null = async value => value as any as R,
  lookupFn: LookupFn<T, R> | null = v => void v,
  validateFn: ValidateFn<T, R> | null = async v => void v,
  errLookupFn: ErrLookupFn | null = v => void v
): Promise<Settled<R>[]>;

generateMapping

generateMapping(collection, TransformFn, LookupFn, ValidateFn, ErrLookupFn): Generator<Promise<Settled<R>>, void, unknown>

Applies the provided callback functions to each item in the collection, and returns a generator that yields promises that resolve to the transformed and validated items, represented as Settled<R> objects.

  • Based on the Generator Protocol

  • Takes as its main input: Iterable<Base<T> | PromiseLike<Base<T>>> only

  • Returns: Generator<Promise<Settled<R>>, void, unknown>

  • Function signature:

export function* generateMapping<T, R>(
  collection: Collection<T>,
  transformFn: TransformFn<T, R> | null = async value => value as any as R,
  lookupFn: LookupFn<T, R> | null = v => void v,
  validateFn: ValidateFn<T, R> | null = async v => void v,
  errLookupFn: ErrLookupFn | null = v => void v
): Generator<Promise<Settled<R>>, void, unknown>;

generateMappingAsync

generateMappingAsync(collection, TransformFn, LookupFn, ValidateFn, ErrLookupFn): AsyncGenerator<Settled<R>, void, unknown>

Applies the provided callback functions to each item in the collection, and returns an async generator that yields the transformed and validated items, represented as Settled<R> objects.

  • Based on the AsyncGenerator Protocol

  • Takes as its main input: Iterable<Base<T> | PromiseLike<Base<T>>> or PromiseLike<Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>>>

  • Returns: AsyncGenerator<Settled<R>, void, unknown>

  • Function signature:

export async function* generateMappingAsync<R, T>(
  collection: DeferredCollection<T>,
  transformFn: TransformFn<T, R> | null = async value => value as any as R,
  lookupFn: LookupFn<T, R> | null = v => void v,
  validateFn: ValidateFn<T, R> | null = async v => void v,
  errLookupFn: ErrLookupFn | null = v => void v
): AsyncGenerator<Settled<R>, void, unknown>;

Return Types

The list of the 5 core function return types is as follows:

Arguments

Our functions have complex signature which are easier to understand when we break them down in ther main coponents:

  • collection: Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>> | PromiseLike<Iterable<Base<T>>>: The collection of items to be iterated or mapped over. The collection can be either an iterable or a combination of an iterable and a promise of an iterable. The Base<T> type represents a resolved or rejected promise, or a value. It can be one of the following:

    • TBase: The resolved value of a promise.
    • Settled<TBase>: An object representing a resolved or rejected promise, with a status field indicating the status of the promise and a value or reason field containing the resolved value or rejection reason, respectively.
    • PromiseSettledResult<TBase>: An object representing a resolved or rejected promise, with a status field indicating the status of the promise and a value or reason field containing the resolved value or rejection reason, respectively.
    • SettledRight<TBase>: An object representing a resolved promise, with a status field equal to 'fulfilled' and a value field containing the resolved value.
    • PromiseFulfilledResult<TBase>: An object representing a resolved promise, with a status field equal to 'fulfilled' and a value field containing the resolved value.
    • SettledLeft: An object representing a rejected promise, with a status field equal to 'rejected' and a reason field containing the rejection reason.
    • PromiseRejectedResult: An object representing a rejected promise, with a status field equal to 'rejected' and a reason field containing the rejection reason.
  • TransformFn<T, R> = async value => value as any as R: A callback function that is applied to each item in the collection. It takes an item of type T as input and returns a value of type R.

  • LookupFn<T, R> = v => void v: A callback function that is applied to each item in the collection. It takes an item of type T as input and returns a value of type R.

  • ValidateFn<T, R> = async v => void v: A callback function that is applied to each item in the collection. It takes an item of type T as input and returns a value of type R.

  • ErrLookupFn = v => void v: A callback function that is applied to each item in the collection. It takes an item of type T as input and returns a value of type R.

/** Type alias either Promise or not */
// Base<B> type is described below...
type AwaitAndBase<B> = Base<B> | PromiseLike<Base<B>>;

type Collection<B> = Iterable<Base<B>>;

Delegates functions

You can provide 4 main types of delegates functions as arguments to the main functions of the package.

In this context delegating just means the act of giving another function the responsibility of carrying out the performance agreed upon in by the folowing interface contracts.

Each delegates can take null or undefined that are repleced by a default value:

const transform: TransformFn<T, R> =
  transformFn == null ? async value => value as any as R : transformFn;

const lookup: LookupFn<T, R> = lookupFn == null ? v => void v : lookupFn;

const validate: ValidateFn<T, R> =
  validateFn == null ? async v => void v : validateFn;

const errLookup: ErrLookupFn = errLookupFn == null ? v => void v : errLookupFn;

transformFn

TransformFn<T,U>

The TransformFn<T,U> delegate function is responsible for carrying out the actual mapping process.

It works similarly to the callback function provided to an Array.prototype.map($), and expects that you will transform from input type T to returned type U.

Proper type annotations are required if you return an unchanged value, as U and T must be different in order to be inferred.

export interface TransformFn<T, U = unknown> {
  (
    value: T,
    index: number,
    array: readonly (T | PromiseSettledResult<T>)[]
  ): Promise<U>;
}

lookupFn

LookupFn<S,U>

The LookupFn<S,U> delegate function is used to acknowledge the transformed value U in an asynchronous manner. The return value of this delegate must be void and must not have any internal side effects within the function where it is executed. However, external side effects, such as a console.log($), are allowed. In this context, OnlySideEffect is an alias for void.

export interface LookupFn<S, U = unknown> {
  (
    value: U,
    index: number,
    array: readonly (S | Settled<S> | PromiseSettledResult<S>)[]
  ): OnlySideEffect;
}

validateFn

ValidateFn<S,U>

The ValidateFn<S,U> delegate function is similar to the LookupFn<S,U> delegate, both of which are optional and should be used only if necessary. The main difference is that the execution of the ValidateFn<S,U> delegate is awaited within the function where it is executed. The return value must be a Promise<void>. The only way to communicate with the main function is to throw a value or exception, which will be caught in the function and returned as a SettledLeft. In this context, Promise<OnlySideEffect> is an alias for Promise<void>. If your fuinction is not returning a promise you can use the async keword as a shortcut to convert to a promise.

export interface ValidateFn<S, U = unknown> {
  (
    value: U,
    index: number,
    array: readonly (S | PromiseSettledResult<S>)[]
  ): Promise<OnlySideEffect>;
}

errLookupFn

ErrLookupFn

The ErrLookupFn delegate function is used to handle errors, and is similar to the LookupFn<S,U> delegate, but for rejections. It takes a currentRejection flag as its third argument, which indicates whether the error occurred during the current iteration or a previous iteration. You should only act on currentRejections that are true, as they are not the result of a previous transformStep or ValidationStep. If you need to access previous rejections, they are also available when the current step is dealing with a previous rejection it will skip the transformFn, lookupFn and validationFn but would be available to the ErrLookupFn even if from a previous transformation step with a currentRejection value of false.

export interface ErrLookupFn {
  (reason: any, index: number, currentRejection: boolean): OnlySideEffect;
}

Internally the delegate is linked via parameter that you can acess from the outside by providing a function (the delegate) of a certain type (specified via an interface) as an argument.

The tranformations, validation and lookup of the resulting operation is mandated to 4 diferet function that let you alter the workflow in 5 diferent areas inside the specific mapping operation over each elements.

Since everything is based only on functions this definition may be different than the usual concept in JavaScript which is related often related to Object composition and inheritance.

Base Types

Base<TVal>

Basic input type, represents the agregation of a naked value of type TBase (i.e T or TVal ), a resolved value wraped in either SettledRight<TBase> | PromiseFulfilledResult<TBase> or a rejection reason wraped in eiter SettledLeft | PromiseRejectedResult or the equivalent unions types Settled<TBase> | PromiseSettledResult<TBase>.

type Base<TBase> =
  | TBase
  | Settled<TBase>
  | PromiseSettledResult<TBase>
  | SettledRight<TBase>
  | PromiseFulfilledResult<TBase>
  | SettledLeft
  | PromiseRejectedResult;

Settled<TVal>

The Settled<TVal> type is an extension of the PromiseSettledResult<TVal> type, and includes additional properties such as the transformStep and currentRejection status. This allows users to more easily track the progress of the transformation process, and quickly identify any errors that may have occurred. Overall, the mapping-tools package is a reliable and robust choice for performing transformations on collections of data.

type Settled<T> = SettledLeft | SettledRight<T>;

SettledRight<TVal>

The SettledRight<TVal> type represents successful transformation results, and includes the fulfilled value as well as additional metadata such as the index of the item in the original collection and the transformation step at which it was resolved. This allows users to easily track and analyze the transformation process, even in cases where errors may have been encountered.

type SettledRight<T> = PromiseFulfilledResult<T> & {
  status: 'fulfilled';
  value: T;

  /* The null value of the transformStep and the index is -1 */
  /* When value is -1 the folowing properties a not enumerated */
  transformStep: number;
  index: number;

  /* Folowing properties a not enumerated (enumerable: false) */
  currentRejection: null;
  fulfilled: T;
  rejected: null;
  reason?: undefined;
};

SettledLeft

On the other hand, the SettledLeft type represents unsuccessful transformation results, and includes the rejected value as well as metadata such as the index and last transformStep. This allows users to easily identify and troubleshoot any issues that may have occurred during the transformation process.

type SettledLeft = PromiseRejectedResult & {
  status: 'rejected';
  reason: any;

  /*
    the currentRejection can be undefined but the property itself
    can not be undefined
   */
  currentRejection: true | false | undefined;

  /* The null value of the transformStep and the index is -1 */
  /* When value is -1 the folowing properties a not enumerated */
  transformStep: number;
  index: number;

  /* Folowing properties a not enumerated (enumerable: false) */
  rejected: any;
  fulfilled: null;
  value?: undefined;
};

PromiseSettledResult<TVal>

/** From typescript lib */
type PromiseSettledResult<T> =
  | PromiseFulfilledResult<T>
  | PromiseRejectedResult;

PromiseFulfilledResult<TVal>

/** From typescript lib */
interface PromiseFulfilledResult<T> {
  status: 'fulfilled';
  value: T;
}

PromiseRejectedResult

/** From typescript lib */
interface PromiseRejectedResult {
  status: 'rejected';
  reason: any;
}

Aliases Types

Deferred<Base>

The Deferred<B> type is an alias for PromiseLike<Base<B>>, which represents a promise-like object that wraps a value of type Base<B>. It is used to simplify the documentation and does not need to be used directly by the end user of the package.

type Deferred<B> = PromiseLike<Base<B>>;

BaseOrDeferred<Base>

BaseOrDeferred<B> is an alias type that represents either a promise-like Deferred<B> type or a Base<B> type.

In some cases the base type can be combined to be either Base<B> or Deferred<B> and would be the type of a parameter to a function that can take such compounded object type.

type BaseOrDeferred<B> = Base<B> | Deferred<B>;

Collection<Base>

Basic Iterable input type, and Iterable version of BaseOrDeferred<Base> the union type of eiter an Iterable<Base<B>> with all its values being of type Base<B> or an Iterable<Deferred<B>> with all its values being of type Deferred<B> pleae not that it is not the same as Iterable<Base<B> | Deferred<B>> as the Iterable canot contain mixed types similar to BaseOrDeferred<Base>;

type Collection<B> = Iterable<Base<B>> | Iterable<Deferred<B>>;

DeferredCollection<Base>

Extended version of basic Iterable input type, may be eiter Collection<B> or a PromiseLike of the same kind PromiseLike<Collection<B>>

type DeferredCollection<B> = Collection<B> | PromiseLike<Collection<B>>;

SettledArray<Result>

type SettledArray<R> = Settled<R>[];

NullSymbol

type NullSymbol = typeof NULL_SYMBOL;

SettledValue<Result>

type SettledValue<R> = R | NullSymbol;

SettledValues<Result>

type SettledValues<R> = SettledValue<R>[];

OnlySideEffect

type OnlySideEffect = void | undefined;

The package has been developed to provide a simple and easy-to-use interface for performing transformations on collections of data. It offers a variety of different functions, including awaitedMapping, parallelMapping, and serialMapping, each with their own specific use cases and features.

One of the key features of the mapping-tools package is its adaptive ability to handle both promises and non-promises as input. This omnivorous feature allows it to be easily integrated into a wide range of contexts and projects, whether dealing with pending values or already resolved values. It simply requires a thenable in a PromiseLike object.

In addition to its flexibility, the package has also been designed with resilience in mind. It includes robust error handling, ensuring that any errors encountered during the transformation process will not prevent the code from providing meaningful output. The output of the package is based on the Settled<TVal> type, which allows users to easily distinguish between successful and unsuccessful results.

The mapping-tools package is a reliable and flexible choice to perform transformations on collections of data.

Contributing

We welcome contributions to Mapping Tools! If you have an idea for a new feature or have found a bug, please open an issue on GitHub. If you'd like to contribute code, please follow these guidelines:

  • All code should be tested using the provided test suite.
  • Please include documentation for any new functions or types you add.

The MIT License (MIT)

The MIT License (MIT)

Copyright © 2022-2023 · LUXCIUM · (Benjamin Vincent Kasapoglu) · luxcium﹫neb401.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.