@ezez/utils 4.3.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/deserialize.d.ts +1 -1
- package/dist/deserialize.d.ts.map +1 -1
- package/dist/deserialize.js +2 -1
- package/dist/deserialize.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/race.d.ts.map +1 -1
- package/dist/race.js.map +1 -1
- package/dist/serialize.d.ts.map +1 -1
- package/dist/serialize.js.map +1 -1
- package/dist/serializeToBuffer/const.d.ts +10 -0
- package/dist/serializeToBuffer/const.d.ts.map +1 -0
- package/dist/serializeToBuffer/const.js +16 -0
- package/dist/serializeToBuffer/const.js.map +1 -0
- package/dist/serializeToBuffer/serializeToBuffer.d.ts +5 -0
- package/dist/serializeToBuffer/serializeToBuffer.d.ts.map +1 -0
- package/dist/serializeToBuffer/serializeToBuffer.js +39 -0
- package/dist/serializeToBuffer/serializeToBuffer.js.map +1 -0
- package/dist/serializeToBuffer/unserializeFromBuffer.d.ts +5 -0
- package/dist/serializeToBuffer/unserializeFromBuffer.d.ts.map +1 -0
- package/dist/serializeToBuffer/unserializeFromBuffer.js +52 -0
- package/dist/serializeToBuffer/unserializeFromBuffer.js.map +1 -0
- package/dist/sortBy.d.ts +1 -1
- package/dist/sortBy.d.ts.map +1 -1
- package/dist/sortBy.js +2 -7
- package/dist/sortBy.js.map +1 -1
- package/dist/sortByMultiple.d.ts +3 -0
- package/dist/sortByMultiple.d.ts.map +1 -0
- package/dist/sortByMultiple.js +18 -0
- package/dist/sortByMultiple.js.map +1 -0
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/documents/CHANGELOG.html +81 -67
- package/docs/functions/index.cap.html +2 -2
- package/docs/functions/index.capitalize.html +2 -2
- package/docs/functions/index.coalesce.html +2 -2
- package/docs/functions/index.compareArrays.html +2 -2
- package/docs/functions/index.compareProps.html +2 -2
- package/docs/functions/index.deserialize.html +2 -2
- package/docs/functions/index.ensureArray.html +2 -2
- package/docs/functions/index.ensureDate.html +2 -2
- package/docs/functions/index.ensureError.html +2 -2
- package/docs/functions/index.ensurePrefix.html +2 -2
- package/docs/functions/index.ensureSuffix.html +2 -2
- package/docs/functions/index.ensureTimestamp.html +2 -2
- package/docs/functions/index.escapeRegExp.html +2 -2
- package/docs/functions/index.formatDate.html +2 -2
- package/docs/functions/index.get.html +2 -2
- package/docs/functions/index.getMultiple.html +2 -2
- package/docs/functions/index.insertSeparator.html +2 -2
- package/docs/functions/index.isEmpty.html +2 -2
- package/docs/functions/index.isNumericString.html +2 -2
- package/docs/functions/index.isPlainObject.html +2 -2
- package/docs/functions/index.last.html +2 -2
- package/docs/functions/index.later-1.html +2 -2
- package/docs/functions/index.mapAsync.html +2 -2
- package/docs/functions/index.mapValues.html +2 -2
- package/docs/functions/index.match.html +2 -2
- package/docs/functions/index.memoize.html +2 -2
- package/docs/functions/index.merge.html +2 -2
- package/docs/functions/index.mostFrequent.html +2 -2
- package/docs/functions/index.noop.html +2 -2
- package/docs/functions/index.occurrences.html +2 -2
- package/docs/functions/index.omit.html +2 -2
- package/docs/functions/index.pick.html +2 -2
- package/docs/functions/index.pull.html +2 -2
- package/docs/functions/index.race.html +6 -3
- package/docs/functions/index.remove.html +2 -2
- package/docs/functions/index.removeCommonProperties.html +2 -2
- package/docs/functions/index.replace.html +2 -2
- package/docs/functions/index.replaceDeep.html +2 -2
- package/docs/functions/index.rethrow.html +2 -2
- package/docs/functions/index.retry.html +2 -2
- package/docs/functions/index.round.html +2 -2
- package/docs/functions/index.safe.html +2 -2
- package/docs/functions/index.sample.html +2 -2
- package/docs/functions/index.samples.html +2 -2
- package/docs/functions/index.scale.html +2 -2
- package/docs/functions/index.seq.html +2 -2
- package/docs/functions/index.seqEarlyBreak.html +2 -2
- package/docs/functions/index.serialize.html +3 -4
- package/docs/functions/index.serializeToBuffer.html +19 -0
- package/docs/functions/index.set.html +2 -2
- package/docs/functions/index.setImmutable.html +2 -2
- package/docs/functions/index.shuffle.html +2 -2
- package/docs/functions/index.sortBy.html +4 -4
- package/docs/functions/index.sortByMultiple.html +10 -0
- package/docs/functions/index.sortProps.html +2 -2
- package/docs/functions/index.stripPrefix.html +2 -2
- package/docs/functions/index.stripSuffix.html +2 -2
- package/docs/functions/index.throttle.html +2 -2
- package/docs/functions/index.toggle.html +2 -2
- package/docs/functions/index.trim.html +2 -2
- package/docs/functions/index.trimEnd.html +2 -2
- package/docs/functions/index.trimStart.html +2 -2
- package/docs/functions/index.truthy.html +2 -2
- package/docs/functions/index.unique.html +2 -2
- package/docs/functions/index.unserializeFromBuffer.html +8 -0
- package/docs/functions/index.wait.html +2 -2
- package/docs/functions/index.waitFor.html +2 -2
- package/docs/functions/index.waitSync.html +2 -2
- package/docs/index.html +2 -2
- package/docs/interfaces/index.ComparePropsOptions.html +3 -3
- package/docs/interfaces/index.GetMultipleSource.html +2 -2
- package/docs/interfaces/index.GetSource.html +2 -2
- package/docs/interfaces/index.IsNumericStringOptions.html +2 -2
- package/docs/interfaces/index.OccurencesOptions.html +2 -2
- package/docs/interfaces/index.SetImmutableSource.html +2 -2
- package/docs/interfaces/index.SetSource.html +2 -2
- package/docs/interfaces/index.ThrottleOptions.html +3 -3
- package/docs/interfaces/index.ThrottledFunctionExtras.html +3 -3
- package/docs/modules/index.html +7 -2
- package/docs/modules.html +2 -2
- package/docs/types/index.CustomDeserializers.html +1 -1
- package/docs/types/index.CustomSerializers.html +1 -1
- package/docs/types/index.Later.html +2 -2
- package/docs/types/index.MapValuesFn.html +2 -2
- package/docs/types/index.MatchCallback.html +1 -1
- package/docs/types/index.MergeTwo.html +2 -2
- package/docs/types/index.SeqEarlyBreaker.html +2 -2
- package/docs/types/index.SeqFn.html +2 -2
- package/docs/types/index.SeqFunctions.html +2 -2
- package/docs/types/index.SetImmutablePath.html +2 -2
- package/docs/types/index.ThrottledFunction.html +1 -1
- package/docs/variables/index.mapValuesUNSET.html +2 -2
- package/docs/variables/index.mergeUNSET.html +2 -2
- package/esm/deserialize.d.ts +1 -1
- package/esm/deserialize.d.ts.map +1 -1
- package/esm/deserialize.js +1 -1
- package/esm/deserialize.js.map +1 -1
- package/esm/index.d.ts +3 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -0
- package/esm/index.js.map +1 -1
- package/esm/race.d.ts.map +1 -1
- package/esm/race.js.map +1 -1
- package/esm/serialize.d.ts.map +1 -1
- package/esm/serialize.js.map +1 -1
- package/esm/serializeToBuffer/const.d.ts +10 -0
- package/esm/serializeToBuffer/const.d.ts.map +1 -0
- package/esm/serializeToBuffer/const.js +10 -0
- package/esm/serializeToBuffer/const.js.map +1 -0
- package/esm/serializeToBuffer/serializeToBuffer.d.ts +5 -0
- package/esm/serializeToBuffer/serializeToBuffer.d.ts.map +1 -0
- package/esm/serializeToBuffer/serializeToBuffer.js +36 -0
- package/esm/serializeToBuffer/serializeToBuffer.js.map +1 -0
- package/esm/serializeToBuffer/unserializeFromBuffer.d.ts +5 -0
- package/esm/serializeToBuffer/unserializeFromBuffer.d.ts.map +1 -0
- package/esm/serializeToBuffer/unserializeFromBuffer.js +48 -0
- package/esm/serializeToBuffer/unserializeFromBuffer.js.map +1 -0
- package/esm/sortBy.d.ts +1 -1
- package/esm/sortBy.d.ts.map +1 -1
- package/esm/sortBy.js +2 -7
- package/esm/sortBy.js.map +1 -1
- package/esm/sortByMultiple.d.ts +3 -0
- package/esm/sortByMultiple.d.ts.map +1 -0
- package/esm/sortByMultiple.js +15 -0
- package/esm/sortByMultiple.js.map +1 -0
- package/package.json +1 -1
- package/src/deserialize.ts +1 -1
- package/src/index.ts +3 -0
- package/src/race.ts +1 -0
- package/src/serialize.ts +1 -2
- package/src/serializeToBuffer/const.ts +16 -0
- package/src/serializeToBuffer/serialization.spec.ts +61 -0
- package/src/serializeToBuffer/serializeToBuffer.spec.ts +43 -0
- package/src/serializeToBuffer/serializeToBuffer.ts +90 -0
- package/src/serializeToBuffer/unserializeFromBuffer.spec.ts +63 -0
- package/src/serializeToBuffer/unserializeFromBuffer.ts +75 -0
- package/src/sortBy.spec.ts +13 -0
- package/src/sortBy.ts +11 -15
- package/src/sortByMultiple.spec.ts +87 -0
- package/src/sortByMultiple.ts +57 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import must from "must"; // eslint-disable-line @typescript-eslint/no-shadow
|
|
2
|
+
|
|
3
|
+
import { unserializeFromBuffer } from "./unserializeFromBuffer";
|
|
4
|
+
|
|
5
|
+
const unserialize = unserializeFromBuffer.bind(null, Buffer, []);
|
|
6
|
+
|
|
7
|
+
describe("unserialize", () => {
|
|
8
|
+
it("unserializes basic string", async () => {
|
|
9
|
+
must(unserialize(Buffer.from([0x34, 0x66, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00]))).eql(["test"]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("unserializes multiple strings", async () => {
|
|
13
|
+
must(unserialize(Buffer.from([
|
|
14
|
+
0x34, 0x66, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00,
|
|
15
|
+
0x34, 0x66, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00,
|
|
16
|
+
]))).eql(["test", "test"]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("unserializes buffer", async () => {
|
|
20
|
+
const result = unserialize(Buffer.from([0x34, 0x74, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00]));
|
|
21
|
+
must(result.length).equal(1);
|
|
22
|
+
must(result[0]).instanceof(Buffer);
|
|
23
|
+
must((result[0] as Buffer).toString()).equal("test");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("unserializes both buffer and string", async () => {
|
|
27
|
+
const result = unserialize(Buffer.from([
|
|
28
|
+
0x34, 0x74, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00,
|
|
29
|
+
0x34, 0x66, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00,
|
|
30
|
+
]));
|
|
31
|
+
|
|
32
|
+
must(result.length).equal(2);
|
|
33
|
+
|
|
34
|
+
must(result[0]).instanceof(Buffer);
|
|
35
|
+
must((result[0] as Buffer).toString()).equal("test");
|
|
36
|
+
|
|
37
|
+
must(result[1]).equal("test");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("unserializes JSON-looking string as string", async () => {
|
|
41
|
+
must(unserialize(
|
|
42
|
+
Buffer.from([0x36, 0x66, 0x00, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x00]),
|
|
43
|
+
)).eql([`"test"`]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("unserializes JSON-serialized string as string", async () => {
|
|
47
|
+
must(unserialize(Buffer.from([
|
|
48
|
+
0x38, 0x66, 0x00, 0x22, 0x73, 0x3a, 0x74, 0x65, 0x73, 0x74, 0x22, 0x00,
|
|
49
|
+
]))).eql([`"s:test"`]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("unserializes JSON-serialized json as string", async () => {
|
|
53
|
+
must(unserialize(Buffer.from([
|
|
54
|
+
0x38, 0x6a, 0x00, 0x22, 0x73, 0x3a, 0x74, 0x65, 0x73, 0x74, 0x22, 0x00,
|
|
55
|
+
]))).eql(["test"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("unserializes number as number", async () => {
|
|
59
|
+
must(unserialize(Buffer.from([
|
|
60
|
+
0x35, 0x6a, 0x00, 0x22, 0x6e, 0x3a, 0x31, 0x22, 0x00,
|
|
61
|
+
]))).eql([1]);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { deserialize } from "../deserialize.js";
|
|
2
|
+
|
|
3
|
+
import { BINARY_MARK_BIN, BINARY_MARK_MAP, BINARY_MARK_STRING } from "./const.js";
|
|
4
|
+
|
|
5
|
+
const MAX_DATA_PARTS = 4;
|
|
6
|
+
|
|
7
|
+
const NOT_FOUND = -1;
|
|
8
|
+
const LAST_CHAR = -1;
|
|
9
|
+
|
|
10
|
+
type DeserializeArgs = Parameters<typeof deserialize> extends [any, ...infer Rest] ? Rest : never; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Unserialize data from a Buffer serialized with {@link serializeToBuffer}, useful when working with web sockets or
|
|
14
|
+
* other binary protocols. Make sure to understand how {@link deserialize} works before using this function.
|
|
15
|
+
* @param BufferImplementation - Buffer implementation, in browsers use `buffer` npm package
|
|
16
|
+
* and import it from `buffer/`, in Node.js simply pass `Buffer`.
|
|
17
|
+
* @param deserializeArgs - [customDeserializers] - optional arguments if you need to unserialize custom data types,
|
|
18
|
+
* @param rawData - Buffer to unserialize
|
|
19
|
+
* @returns unserialized data
|
|
20
|
+
*/
|
|
21
|
+
const unserializeFromBuffer = <RT extends any[] = unknown[]>( // eslint-disable-line @typescript-eslint/no-explicit-any,max-statements
|
|
22
|
+
BufferImplementation: typeof Buffer, deserializeArgs: DeserializeArgs, rawData: Buffer | Uint8Array,
|
|
23
|
+
): RT => {
|
|
24
|
+
const intData: Uint8Array = rawData instanceof Uint8Array ? rawData : new Uint8Array(rawData);
|
|
25
|
+
|
|
26
|
+
if (intData.length === 0) {
|
|
27
|
+
throw new Error("No data to unserialize");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let startPoint = 0;
|
|
31
|
+
const result = [];
|
|
32
|
+
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i++ < MAX_DATA_PARTS) {
|
|
35
|
+
const dataSplitPoint = intData.indexOf(0, startPoint); // find null
|
|
36
|
+
if (dataSplitPoint === NOT_FOUND) { // no null found = no data
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lengthBytes = BufferImplementation.from(intData.slice(startPoint, dataSplitPoint)).toString("utf8");
|
|
41
|
+
const binaryMark = lengthBytes.slice(LAST_CHAR) as keyof typeof BINARY_MARK_MAP;
|
|
42
|
+
if (!Object.keys(BINARY_MARK_MAP).includes(binaryMark)) {
|
|
43
|
+
throw new Error(`Invalid binary mark: ${binaryMark}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const len = Number(lengthBytes.slice(0, lengthBytes.length - 1));
|
|
47
|
+
|
|
48
|
+
const dataStringFrom = dataSplitPoint + 1;
|
|
49
|
+
const dataStringTo = dataStringFrom + len;
|
|
50
|
+
const dataBytes = intData.slice(dataStringFrom, dataStringTo);
|
|
51
|
+
|
|
52
|
+
if (binaryMark === BINARY_MARK_BIN) {
|
|
53
|
+
result.push(dataBytes);
|
|
54
|
+
}
|
|
55
|
+
else if (binaryMark === BINARY_MARK_STRING) {
|
|
56
|
+
const stringData = BufferImplementation.from(dataBytes).toString("utf8");
|
|
57
|
+
result.push(stringData);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const stringData = BufferImplementation.from(dataBytes).toString("utf8");
|
|
61
|
+
const jsonData = deserialize(stringData, ...deserializeArgs);
|
|
62
|
+
result.push(jsonData);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
startPoint = dataStringTo + 1; // skip separating null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (result.length === 0) {
|
|
69
|
+
throw new Error("No data found (no split points)");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result as RT;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export { unserializeFromBuffer, unserializeFromBuffer as deserializeFromBuffer };
|
package/src/sortBy.spec.ts
CHANGED
|
@@ -53,4 +53,17 @@ describe("sortBy", () => {
|
|
|
53
53
|
{ size: 90 },
|
|
54
54
|
]);
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
it("supports using default value", () => {
|
|
58
|
+
const sizes = [
|
|
59
|
+
{ size: 30 },
|
|
60
|
+
{ size: 90 },
|
|
61
|
+
{},
|
|
62
|
+
];
|
|
63
|
+
sizes.sort(sortBy("size", true, 55)).must.eql([
|
|
64
|
+
{ size: 30 },
|
|
65
|
+
{}, // size assumed to be 55
|
|
66
|
+
{ size: 90 },
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
56
69
|
});
|
package/src/sortBy.ts
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
|
+
import { sortByMultiple } from "./sortByMultiple.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Returns a function that can be used as a callback to `.sort()` method. Returned function will sort array by given
|
|
3
5
|
* property.
|
|
4
|
-
*
|
|
5
|
-
* @param
|
|
6
|
-
* @param
|
|
6
|
+
*
|
|
7
|
+
* @param propertyName - name of the property to sort by
|
|
8
|
+
* @param asc - should sort be ascending?
|
|
9
|
+
* @param defaultValue - value to use when property is not defined in one of the objects
|
|
7
10
|
*/
|
|
8
|
-
const sortBy = <T
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if ((a[propertyName] ?? defaultValue) > (b[propertyName] ?? defaultValue)) {
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
|
16
|
-
return asc ? 1 : -1;
|
|
17
|
-
}
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
|
19
|
-
return asc ? -1 : 1;
|
|
11
|
+
const sortBy = <T extends Record<string | number | symbol, any>>( // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
12
|
+
propertyName: keyof T, asc = true, defaultValue: unknown = null,
|
|
13
|
+
) => (a: T, b: T) => {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
15
|
+
return sortByMultiple([propertyName], asc, { [propertyName]: defaultValue } as Partial<Record<keyof T, any>>)(a, b);
|
|
20
16
|
};
|
|
21
17
|
|
|
22
18
|
export {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { sortByMultiple } from "./sortByMultiple.js";
|
|
2
|
+
|
|
3
|
+
describe("sortByMultiple", () => {
|
|
4
|
+
const items = [
|
|
5
|
+
{ height: 480, bitrate: 300 },
|
|
6
|
+
{ height: 720, bitrate: 100 },
|
|
7
|
+
{ height: 720, bitrate: 200 },
|
|
8
|
+
{ height: 720, bitrate: 50 },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
it("must sort asc", () => {
|
|
12
|
+
items.sort(sortByMultiple(["height", "bitrate"])).must.eql([
|
|
13
|
+
{ height: 480, bitrate: 300 },
|
|
14
|
+
{ height: 720, bitrate: 50 },
|
|
15
|
+
{ height: 720, bitrate: 100 },
|
|
16
|
+
{ height: 720, bitrate: 200 },
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("must sort desc", () => {
|
|
21
|
+
items.sort(sortByMultiple(["height", "bitrate"], false)).must.eql([
|
|
22
|
+
{ height: 720, bitrate: 200 },
|
|
23
|
+
{ height: 720, bitrate: 100 },
|
|
24
|
+
{ height: 720, bitrate: 50 },
|
|
25
|
+
{ height: 480, bitrate: 300 },
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("must sort with default value (example 1)", () => {
|
|
30
|
+
const itemsMissing = [
|
|
31
|
+
{ height: 480, bitrate: 300 },
|
|
32
|
+
{ height: 720, bitrate: 100 },
|
|
33
|
+
{ height: 720 },
|
|
34
|
+
{ height: 720, bitrate: 200 },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
itemsMissing.sort(sortByMultiple(["height", "bitrate"], false, { bitrate: 999 })).must.eql([
|
|
38
|
+
{ height: 720 }, // bitrate assumed to be 999
|
|
39
|
+
{ height: 720, bitrate: 200 },
|
|
40
|
+
{ height: 720, bitrate: 100 },
|
|
41
|
+
{ height: 480, bitrate: 300 },
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("must sort with default value (example 2)", () => {
|
|
46
|
+
const itemsMissing = [
|
|
47
|
+
{ height: 480, bitrate: 300 },
|
|
48
|
+
{ height: 720, bitrate: 100 },
|
|
49
|
+
{ height: 720 },
|
|
50
|
+
{ height: 720, bitrate: 200 },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
itemsMissing.sort(sortByMultiple(["height", "bitrate"], true, { bitrate: 1 })).must.eql([
|
|
54
|
+
{ height: 480, bitrate: 300 },
|
|
55
|
+
{ height: 720 }, // bitrate assumed to be 1
|
|
56
|
+
{ height: 720, bitrate: 100 },
|
|
57
|
+
{ height: 720, bitrate: 200 },
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("must sort each prop order separately", () => {
|
|
62
|
+
items.sort(sortByMultiple(["height", "bitrate"], { height: true, bitrate: false })).must.eql([
|
|
63
|
+
{ height: 480, bitrate: 300 },
|
|
64
|
+
{ height: 720, bitrate: 200 },
|
|
65
|
+
{ height: 720, bitrate: 100 },
|
|
66
|
+
{ height: 720, bitrate: 50 },
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("must sort each prop order separately (with missing)", () => {
|
|
71
|
+
const itemsMissing = [
|
|
72
|
+
{ height: 480, bitrate: 300 },
|
|
73
|
+
{ height: 720, bitrate: 100 },
|
|
74
|
+
{ height: 720 },
|
|
75
|
+
{ height: 720, bitrate: 200 },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
itemsMissing.sort(
|
|
79
|
+
sortByMultiple(["height", "bitrate"], { height: true, bitrate: false }, { bitrate: 150 }),
|
|
80
|
+
).must.eql([
|
|
81
|
+
{ height: 480, bitrate: 300 },
|
|
82
|
+
{ height: 720, bitrate: 200 },
|
|
83
|
+
{ height: 720 }, // bitrate assumed to be 150
|
|
84
|
+
{ height: 720, bitrate: 100 },
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a function that can be used as a callback to `.sort()` method. Returned function will sort array by given
|
|
3
|
+
* properties.
|
|
4
|
+
*
|
|
5
|
+
* @param propertyNames - name of the properties to sort by, in order of priority
|
|
6
|
+
* @param ascending - should sort be ascending? Pass boolean or object with boolean values for each property
|
|
7
|
+
* @param defaultValues - values to use when properties are not defined in one of the objects
|
|
8
|
+
*
|
|
9
|
+
* @example:
|
|
10
|
+
* ```
|
|
11
|
+
* const videos = [
|
|
12
|
+
* { height: 480, bitrate: 300 },
|
|
13
|
+
* { height: 720, bitrate: 100 },
|
|
14
|
+
* { height: 720 },
|
|
15
|
+
* { height: 720, bitrate: 200 },
|
|
16
|
+
* ];
|
|
17
|
+
* // Sort by height ascending, then by bitrate descending, and set default bitrate to 150
|
|
18
|
+
* itemsMissing.sort(sortByMultiple(["height", "bitrate"], { height: true, bitrate: false }, { bitrate: 150 }));
|
|
19
|
+
* // Result:
|
|
20
|
+
* [
|
|
21
|
+
* { height: 480, bitrate: 300 },
|
|
22
|
+
* { height: 720, bitrate: 200 },
|
|
23
|
+
* { height: 720 }, // bitrate assumed to be 150
|
|
24
|
+
* { height: 720, bitrate: 100 },
|
|
25
|
+
* ]
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
const sortByMultiple = <
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
T extends Record<string | number | symbol, any>,
|
|
31
|
+
K extends (keyof T)[],
|
|
32
|
+
>(
|
|
33
|
+
propertyNames: K,
|
|
34
|
+
ascending: boolean | Record<K[number], boolean> = true,
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
defaultValues?: Partial<Record<K[number], any>>, // Force defaultValues to use only keys from K
|
|
37
|
+
): ((a: T, b: T) => number) => (a: T, b: T) => {
|
|
38
|
+
for (const propertyName of propertyNames) {
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
40
|
+
const asc = typeof ascending === "boolean" ? ascending : (ascending[propertyName] ?? true);
|
|
41
|
+
const aBiggerValue = asc ? 1 : -1; // eslint-disable-line @typescript-eslint/no-magic-numbers
|
|
42
|
+
const bBiggerValue = asc ? -1 : 1; // eslint-disable-line @typescript-eslint/no-magic-numbers
|
|
43
|
+
|
|
44
|
+
const aValue = a[propertyName] ?? defaultValues?.[propertyName];
|
|
45
|
+
const bValue = b[propertyName] ?? defaultValues?.[propertyName];
|
|
46
|
+
|
|
47
|
+
if (aValue !== bValue) {
|
|
48
|
+
// @ts-expect-error Too dynamic for TS
|
|
49
|
+
return (aValue > bValue ? aBiggerValue : bBiggerValue);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return 0;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
sortByMultiple,
|
|
57
|
+
};
|