// Copyright IBM Corp. and LoopBack contributors 2019. All Rights Reserved. // Node module: @loopback/context // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import {Binding} from './binding'; /** * Compare function to sort an array of bindings. * It is used by `Array.prototype.sort()`. * * @example * ```ts * const compareByKey: BindingComparator = (a, b) => a.key.localeCompare(b.key); * ``` */ export interface BindingComparator { /** * Compare two bindings * @param bindingA - First binding * @param bindingB - Second binding * @returns A number to determine order of bindingA and bindingB * - 0 leaves bindingA and bindingB unchanged * - <0 bindingA comes before bindingB * - >0 bindingA comes after bindingB */ ( bindingA: Readonly>, bindingB: Readonly>, ): number; } /** * Creates a binding compare function to sort bindings by tagged phase name. * * @remarks * Two bindings are compared as follows: * * 1. Get values for the given tag as `phase` for bindings, if the tag is not * present, default `phase` to `''`. * 2. If both bindings have `phase` value in `orderOfPhases`, honor the order * specified by `orderOfPhases`. * 3. If a binding's `phase` does not exist in `orderOfPhases`, it comes before * the one with `phase` exists in `orderOfPhases`. * 4. If both bindings have `phase` value outside of `orderOfPhases`, they are * ordered by phase names alphabetically and symbol values come before string * values. * * @param phaseTagName - Name of the binding tag for phase * @param orderOfPhases - An array of phase names as the predefined order */ export function compareBindingsByTag( phaseTagName = 'phase', orderOfPhases: (string | symbol)[] = [], ): BindingComparator { return (a: Readonly>, b: Readonly>) => { return compareByOrder( a.tagMap[phaseTagName], b.tagMap[phaseTagName], orderOfPhases, ); }; } /** * Compare two values by the predefined order * * @remarks * * The comparison is performed as follows: * * 1. If both values are included in `order`, they are sorted by their indexes in * `order`. * 2. The value included in `order` comes after the value not included in `order`. * 3. If neither values are included in `order`, they are sorted: * - symbol values come before string values * - alphabetical order for two symbols or two strings * * @param a - First value * @param b - Second value * @param order - An array of values as the predefined order */ export function compareByOrder( a: string | symbol | undefined | null, b: string | symbol | undefined | null, order: (string | symbol)[] = [], ) { a = a ?? ''; b = b ?? ''; const i1 = order.indexOf(a); const i2 = order.indexOf(b); if (i1 !== -1 || i2 !== -1) { // Honor the order return i1 - i2; } else { // Neither value is in the pre-defined order // symbol comes before string if (typeof a === 'symbol' && typeof b === 'string') return -1; if (typeof a === 'string' && typeof b === 'symbol') return 1; // both a and b are symbols or both a and b are strings if (typeof a === 'symbol') a = a.toString(); if (typeof b === 'symbol') b = b.toString(); return a < b ? -1 : a > b ? 1 : 0; } } /** * Sort bindings by phase names denoted by a tag and the predefined order * * @param bindings - An array of bindings * @param phaseTagName - Tag name for phase, for example, we can use the value * `'a'` of tag `order` as the phase name for `binding.tag({order: 'a'})`. * * @param orderOfPhases - An array of phase names as the predefined order */ export function sortBindingsByPhase( bindings: Readonly>[], phaseTagName?: string, orderOfPhases?: (string | symbol)[], ) { return bindings.sort(compareBindingsByTag(phaseTagName, orderOfPhases)); }