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.
- Mapping Tools
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-toolsThe 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.
- awaitedMapping, is based on Promise.all($)
- parallelMapping, is based on Array.prototype.map($)
- serialMapping, is based on a forOf loop
- generateMapping, is based on the Generator protocol
- 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.
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';The easiest way to use mapping-tools is with the fluent, chainable API:
import { chain } from 'mapping-tools';
// Simple transformation chain
const result = await chain([1, 2, 3, 4, 5])
.awaitedMapping(async x => x * 2)
.awaitedMapping(async x => x + 1)
.getValues();
// result: [3, 5, 7, 9, 11]
// With error handling
const results = await chain([1, 2, 3, 4])
.awaitedMapping(async x => {
if (x === 2) throw new Error('Skip 2');
return x * 2;
})
.getValues(); // Only successful values: [2, 6, 8]You can also use the standalone functions for more control:
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
*/The chainable API provides a fluent, ergonomic interface for composing transformations. This is the recommended approach for most use cases as it reduces cognitive load and makes complex transformations easier to read and maintain.
You can create a chain in two ways:
import { chain, Chain } from 'mapping-tools';
// Using the chain() helper function (recommended)
const result1 = await chain([1, 2, 3, 4, 5])
.awaitedMapping(async x => x * 2)
.getValues();
// Using Chain.of() static method
const result2 = await Chain.of([1, 2, 3, 4, 5])
.awaitedMapping(async x => x * 2)
.getValues();
// Works with promises too
const promisedArray = Promise.resolve([1, 2, 3]);
const result3 = await chain(promisedArray)
.awaitedMapping(async x => x * 2)
.getValues();All the core mapping functions are available as chainable methods:
// awaitedMapping - parallel transformations using Promise.all
await chain([1, 2, 3])
.awaitedMapping(async x => x * 2)
.awaitedMapping(async x => x + 1)
.getValues();
// Result: [3, 5, 7]
// serialMapping - sequential transformations
await chain([1, 2, 3])
.serialMapping(async x => {
await someAsyncOperation();
return x * 2;
})
.getValues();
// parallelMapping - returns array of promises
await chain([1, 2, 3])
.parallelMapping(async x => x * 2)
.toArray();
// You can mix different mapping types
await chain([1, 2, 3])
.awaitedMapping(async x => x * 2)
.serialMapping(async x => x + 10)
.awaitedMapping(async x => x / 2)
.getValues();
// Result: [6, 7, 8]The chain provides multiple methods to extract the final results:
const data = [1, 2, 3, 4, 5];
// getValues() - Get only successful values (recommended)
// Filters out any rejected/failed transformations
const values = await chain(data)
.awaitedMapping(async x => {
if (x === 3) throw new Error('Skip 3');
return x * 2;
})
.getValues();
// Result: [2, 4, 8, 10] - element at index 2 was filtered out
// toArray() - Get complete settled results
// Returns Settled<T>[] with both fulfilled and rejected entries
const settled = await chain(data)
.awaitedMapping(async x => x * 2)
.toArray();
// Result: Array of Settled objects with status, value/reason, index, etc.
// getAllValues() - Get all values with NULL_SYMBOL for failures
// Maintains original array length and positions
const allValues = await chain(data)
.awaitedMapping(async x => {
if (x === 3) throw new Error('Skip 3');
return x * 2;
})
.getAllValues();
// Result: [2, 4, Symbol(null), 8, 10] - preserves positionThe chainable API provides robust error handling:
// Errors are captured and don't stop the chain
const result = await chain([1, 2, 3, 4, 5])
.awaitedMapping(async x => {
if (x % 2 === 0) throw new Error('Even number');
return x * 2;
})
.getValues();
// Result: [2, 6, 10] - only odd numbers (even ones were rejected)
// You can filter fulfilled and rejected values separately
const withErrors = chain([1, 2, 3, 4]);
const fulfilled = await withErrors
.awaitedMapping(async x => {
if (x === 2) throw new Error('Error');
return x;
})
.filterRight();
// fulfilled contains only SettledRight entries
const rejected = await withErrors
.awaitedMapping(async x => {
if (x === 2) throw new Error('Error');
return x;
})
.filterLeft();
// rejected contains only SettledLeft entries
// Errors propagate through multiple transformations
const result2 = await chain([1, 2, 3])
.awaitedMapping(async x => {
if (x === 2) throw new Error('Error at step 1');
return x * 2;
})
.awaitedMapping(async x => x + 10) // This won't transform the failed entry
.toArray();
// result2[1] will still be rejected from the first transformation// Using validation and lookup functions
await chain([1, 2, 3, 4, 5])
.awaitedMapping(
async x => x * 2, // transform
value => console.log('Got:', value), // lookup (side effect)
async value => { // validate
if (value > 6) throw new Error('Too large');
},
(reason, index) => { // error lookup
console.log(`Error at ${index}:`, reason);
}
)
.getValues();
// Working with complex async operations
const result = await chain([1, 2, 3, 4, 5])
.awaitedMapping(async x => {
const data = await fetchData(x);
return data.value;
})
.awaitedMapping(async value => {
const processed = await processData(value);
return processed;
})
.getValues();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:
- Functions that can accept either
Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>>orPromiseLike<Iterable<Base<T>>>and which return promises that resolve to arrays: serialMapping, awaitedMapping, and generateMappingAsync returns - Functions that can accept only
Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>>and return arrays of promises or generators: parallelMapping returns, generateMapping returns
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(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
forOfloop -
Takes as its main input:
Iterable<Base<T>> | Iterable<PromiseLike<Base<T>>>orPromiseLike<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(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>>>orPromiseLike<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(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
GeneratorProtocol -
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(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
AsyncGeneratorProtocol -
Takes as its main input:
Iterable<Base<T> | PromiseLike<Base<T>>>orPromiseLike<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>;The list of the 5 core function return types is as follows:
-
Functions that return arrays
- parallelMapping returns:
Array<Promise<Settled<R>>>
- parallelMapping returns:
-
Functions that return promises that resolve to arrays
- serialMapping returns:
Promise<Array<Settled<R>>> - awaitedMapping returns:
Promise<Array<Settled<R>>>
- serialMapping returns:
-
Functions that return generators
- generateMapping returns:
Generator<Promise<Settled<R>>, void, unknown> - generateMappingAsync returns:
AsyncGenerator<Settled<R>, void, unknown>
- generateMapping returns:
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. TheBase<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 astatusfield indicating the status of the promise and avalueorreasonfield containing the resolved value or rejection reason, respectively.PromiseSettledResult<TBase>: An object representing a resolved or rejected promise, with astatusfield indicating the status of the promise and avalueorreasonfield containing the resolved value or rejection reason, respectively.SettledRight<TBase>: An object representing a resolved promise, with astatusfield equal to'fulfilled'and avaluefield containing the resolved value.PromiseFulfilledResult<TBase>: An object representing a resolved promise, with astatusfield equal to'fulfilled'and avaluefield containing the resolved value.SettledLeft: An object representing a rejected promise, with astatusfield equal to'rejected'and areasonfield containing the rejection reason.PromiseRejectedResult: An object representing a rejected promise, with astatusfield equal to'rejected'and areasonfield 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 typeTas input and returns a value of typeR. -
LookupFn<T, R> = v => void v: A callback function that is applied to each item in the collection. It takes an item of typeTas input and returns a value of typeR. -
ValidateFn<T, R> = async v => void v: A callback function that is applied to each item in the collection. It takes an item of typeTas input and returns a value of typeR. -
ErrLookupFn = v => void v: A callback function that is applied to each item in the collection. It takes an item of typeTas input and returns a value of typeR.
/** 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>>;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<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<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<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
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.
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;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>;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;
};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 following properties a not enumerated */
transformStep: number;
index: number;
/* Following properties a not enumerated (enumerable: false) */
rejected: any;
fulfilled: null;
value?: undefined;
};/** From typescript lib */
type PromiseSettledResult<T> =
| PromiseFulfilledResult<T>
| PromiseRejectedResult;/** From typescript lib */
interface PromiseFulfilledResult<T> {
status: 'fulfilled';
value: T;
}/** From typescript lib */
interface PromiseRejectedResult {
status: 'rejected';
reason: any;
}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<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>;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>>;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>>;type SettledArray<R> = Settled<R>[];type NullSymbol = typeof NULL_SYMBOL;type SettledValue<R> = R | NullSymbol;type SettledValues<R> = SettledValue<R>[];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.
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)
Copyright © 2022-2024 · 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.
Text generated by an AI language model has been used to help create parts of the documentation.