UNPKG

3.98 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2019. All Rights Reserved.
2// Node module: @loopback/context
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6import {Binding} from './binding';
7
8/**
9 * Compare function to sort an array of bindings.
10 * It is used by `Array.prototype.sort()`.
11 *
12 * @example
13 * ```ts
14 * const compareByKey: BindingComparator = (a, b) => a.key.localeCompare(b.key);
15 * ```
16 */
17export interface BindingComparator {
18 /**
19 * Compare two bindings
20 * @param bindingA - First binding
21 * @param bindingB - Second binding
22 * @returns A number to determine order of bindingA and bindingB
23 * - 0 leaves bindingA and bindingB unchanged
24 * - <0 bindingA comes before bindingB
25 * - >0 bindingA comes after bindingB
26 */
27 (
28 bindingA: Readonly<Binding<unknown>>,
29 bindingB: Readonly<Binding<unknown>>,
30 ): number;
31}
32
33/**
34 * Creates a binding compare function to sort bindings by tagged phase name.
35 *
36 * @remarks
37 * Two bindings are compared as follows:
38 *
39 * 1. Get values for the given tag as `phase` for bindings, if the tag is not
40 * present, default `phase` to `''`.
41 * 2. If both bindings have `phase` value in `orderOfPhases`, honor the order
42 * specified by `orderOfPhases`.
43 * 3. If a binding's `phase` does not exist in `orderOfPhases`, it comes before
44 * the one with `phase` exists in `orderOfPhases`.
45 * 4. If both bindings have `phase` value outside of `orderOfPhases`, they are
46 * ordered by phase names alphabetically and symbol values come before string
47 * values.
48 *
49 * @param phaseTagName - Name of the binding tag for phase
50 * @param orderOfPhases - An array of phase names as the predefined order
51 */
52export function compareBindingsByTag(
53 phaseTagName = 'phase',
54 orderOfPhases: (string | symbol)[] = [],
55): BindingComparator {
56 return (a: Readonly<Binding<unknown>>, b: Readonly<Binding<unknown>>) => {
57 return compareByOrder(
58 a.tagMap[phaseTagName],
59 b.tagMap[phaseTagName],
60 orderOfPhases,
61 );
62 };
63}
64
65/**
66 * Compare two values by the predefined order
67 *
68 * @remarks
69 *
70 * The comparison is performed as follows:
71 *
72 * 1. If both values are included in `order`, they are sorted by their indexes in
73 * `order`.
74 * 2. The value included in `order` comes after the value not included in `order`.
75 * 3. If neither values are included in `order`, they are sorted:
76 * - symbol values come before string values
77 * - alphabetical order for two symbols or two strings
78 *
79 * @param a - First value
80 * @param b - Second value
81 * @param order - An array of values as the predefined order
82 */
83export function compareByOrder(
84 a: string | symbol | undefined | null,
85 b: string | symbol | undefined | null,
86 order: (string | symbol)[] = [],
87) {
88 a = a ?? '';
89 b = b ?? '';
90 const i1 = order.indexOf(a);
91 const i2 = order.indexOf(b);
92 if (i1 !== -1 || i2 !== -1) {
93 // Honor the order
94 return i1 - i2;
95 } else {
96 // Neither value is in the pre-defined order
97
98 // symbol comes before string
99 if (typeof a === 'symbol' && typeof b === 'string') return -1;
100 if (typeof a === 'string' && typeof b === 'symbol') return 1;
101
102 // both a and b are symbols or both a and b are strings
103 if (typeof a === 'symbol') a = a.toString();
104 if (typeof b === 'symbol') b = b.toString();
105 return a < b ? -1 : a > b ? 1 : 0;
106 }
107}
108
109/**
110 * Sort bindings by phase names denoted by a tag and the predefined order
111 *
112 * @param bindings - An array of bindings
113 * @param phaseTagName - Tag name for phase, for example, we can use the value
114 * `'a'` of tag `order` as the phase name for `binding.tag({order: 'a'})`.
115 *
116 * @param orderOfPhases - An array of phase names as the predefined order
117 */
118export function sortBindingsByPhase<T = unknown>(
119 bindings: Readonly<Binding<T>>[],
120 phaseTagName?: string,
121 orderOfPhases?: (string | symbol)[],
122) {
123 return bindings.sort(compareBindingsByTag(phaseTagName, orderOfPhases));
124}