import { MaybeAsyncConverter } from "../types.js"; import { Nullable } from "../utils.js"; import { AsyncComparer, MaybeAsyncComparisonOrComparer, Comparer, MaybeAsyncComparison, AsyncComparison } from "./types.js"; export function isAsyncComparer(obj: any): obj is AsyncComparer { return obj instanceof BaseAsyncComparer; } export function asAsyncComparer(comparer: MaybeAsyncComparisonOrComparer): AsyncComparer { return typeof comparer === "function" ? createAsyncComparer(comparer) : isAsyncComparer(comparer) ? comparer : new WrappedAsyncComparer(comparer); } export function fromSync(comparer: Comparer): AsyncComparer { return new WrappedAsyncComparer(comparer); } export function createAsyncComparer(comparison: MaybeAsyncComparison): AsyncComparer { return new SimpleAsyncComparer(comparison); } export function createAsyncComparerUsing(projection: MaybeAsyncConverter, comparison?: MaybeAsyncComparisonOrComparer): AsyncComparer { return new MappedAsyncComparer(projection, comparison); } export function combineNullableAsyncComparers(comparers: Nullable>[]): AsyncComparer | undefined { let result = defaultAsyncComparer; for (const comparer of comparers) { if (!comparer) { continue; } result = result.then(asAsyncComparer(comparer)); } return result === defaultAsyncComparer ? undefined : result; } export abstract class BaseAsyncComparer implements AsyncComparer { #cachedBoundComparison: AsyncComparison | undefined; public abstract compare(a: T, b: T): Promise; public comparison(): AsyncComparison { return this.#cachedBoundComparison ??= this.compare.bind(this); } public reverse(): AsyncComparer { return new ReversedAsyncComparer(this); } public then(comparer: AsyncComparer): AsyncComparer { return new ThenAsyncComparer(this, comparer); } public thenCompare(comparison: MaybeAsyncComparison): AsyncComparer { return this.then(createAsyncComparer(comparison)); } public thenCompareUsing(projection: MaybeAsyncConverter, comparer?: MaybeAsyncComparisonOrComparer): AsyncComparer { return this.then(createAsyncComparerUsing(projection, comparer)); } public nullAwareComparer(): AsyncComparer> { return new NullAwareAsyncComparer(this); } } class WrappedAsyncComparer extends BaseAsyncComparer { readonly #base: Comparer; constructor(base: Comparer) { super(); this.#base = base; } public override async compare(a: T, b: T) { return this.#base.compare(a, b); } } class SimpleAsyncComparer extends BaseAsyncComparer { readonly #comparison: MaybeAsyncComparison; public constructor(comparison: MaybeAsyncComparison) { super(); this.#comparison = comparison; } public async compare(a: T, b: T) { return await this.#comparison(a, b); } } class MappedAsyncComparer extends BaseAsyncComparer { readonly #projection: MaybeAsyncConverter; readonly #comparer: AsyncComparer; public constructor(projection: MaybeAsyncConverter, comparer?: MaybeAsyncComparisonOrComparer) { super(); this.#projection = projection; this.#comparer = comparer ? asAsyncComparer(comparer) : defaultAsyncComparer; } public async compare(a: T, b: T) { return await this.#comparer.compare(await this.#projection(a), await this.#projection(b)); } } class ReversedAsyncComparer extends BaseAsyncComparer { readonly #base: AsyncComparer; public constructor(base: AsyncComparer) { super(); this.#base = base; } public async compare(a: T, b: T) { return await this.#base.compare(b, a); } public override reverse() { return this.#base; } } class ThenAsyncComparer extends BaseAsyncComparer { readonly #base: AsyncComparer; readonly #comparer: AsyncComparer; public constructor(base: AsyncComparer, comparer: AsyncComparer) { super(); this.#base = base; this.#comparer = comparer; } public async compare(a: T, b: T) { return await this.#base.compare(a, b) || await this.#comparer.compare(a, b); } } export const defaultAsyncComparer: AsyncComparer = new class DefaultAsyncComparer extends BaseAsyncComparer { public override async compare(a: any, b: any): Promise { if (a === undefined) { if (b === undefined) { return 0; } return 1; } if (b === undefined) { return -1; } const aStr = `${a}`, bStr = `${b}`; return aStr > bStr ? 1 : aStr < bStr ? -1 : 0; } }; export function getDefaultAsyncComparer(): AsyncComparer { return defaultAsyncComparer; } class NullAwareAsyncComparer extends BaseAsyncComparer> { readonly #base: AsyncComparer; constructor(baseComparer: AsyncComparer) { super(); this.#base = baseComparer; } public override async compare(a: Nullable, b: Nullable): Promise { if (a === undefined) { if (b === undefined) { return 0; } return 1; } if (b === undefined) { return -1; } if (a === null) { if (b === null) { return 0; } return 1; } if (b === null) { return -1; } return await this.#base.compare(a, b); } public override nullAwareComparer(): AsyncComparer> { return this; } } export function nullAwareComparer(baseComparer: AsyncComparer): AsyncComparer> { return new NullAwareAsyncComparer(baseComparer); } export const stringAsyncComparer: AsyncComparer = new class StringAsyncComparer extends BaseAsyncComparer { public override async compare(a: string, b: string): Promise { return a.localeCompare(b); } }; export const numberAsyncComparer: AsyncComparer = new class NumberAsyncComparer extends BaseAsyncComparer { public override async compare(a: number, b: number): Promise { return a - b; } }; export const bigintAsyncComparer: AsyncComparer = new class BigIntAsyncComparer extends BaseAsyncComparer { public override async compare(a: bigint, b: bigint): Promise { return Number(a - b); } }; export const booleanAsyncComparer: AsyncComparer = new class BooleanAsyncComparer extends BaseAsyncComparer { public override async compare(a: boolean, b: boolean): Promise { return Number(a) - Number(b); } }; export const dateAsyncComparer: AsyncComparer = new class DateAsyncComparer extends BaseAsyncComparer { public override async compare(a: Date, b: Date): Promise { return a.getTime() - b.getTime(); } };