mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Convert runMany and runManySync to use injectManyWithMeta + move to seperate package (#7244)
* Convert runMany and runManySync to use injectManyWithMeta Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup type errors due to new Runnable requirements Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add documentation for verifyRunnablesAreDAG Signed-off-by: Sebastian Malton <sebastian@malton.name> * Simplify convertToWithIdWith Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move all utility functions to separate package Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move testing utilities to separate package Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move run-many and run-many-sync to separate package Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace all internal uses of utilities with new packages Signed-off-by: Sebastian Malton <sebastian@malton.name> * Use new @k8slens/run-many package in core Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add dep to open-lens Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup uses of @k8slens/test-utils Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup getGlobalOverride Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move tests to new package too Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup uses of AsyncResult and autoBind Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup remaining import issues Signed-off-by: Sebastian Malton <sebastian@malton.name> * Finial fixups to fix build Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * Revert moving "testUsingFakeTime" to separate package - This fixes tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix integration tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit test failing due to spelling fix Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
ad9bafe2a5
commit
2789bcebcb
754 files changed
+2715
-2752
No files matched your search
@@ -1,128 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { bytesToUnits, unitsToBytes } from "../convertMemory";
|
||||
|
||||
describe("unitsToBytes", () => {
|
||||
it("without any units, just parse as float", () => {
|
||||
expect(unitsToBytes("1234")).toBe(1234);
|
||||
});
|
||||
|
||||
it("should parse with B suffix", () => {
|
||||
expect(unitsToBytes("1234B")).toBe(1234);
|
||||
});
|
||||
|
||||
it("should parse with Ki suffix", () => {
|
||||
expect(unitsToBytes("1234Ki")).toBe(1234 * (1024));
|
||||
});
|
||||
|
||||
it("should parse with Ki suffix as the same as KiB", () => {
|
||||
expect(unitsToBytes("1234Ki")).toBe(unitsToBytes("1234KiB"));
|
||||
});
|
||||
|
||||
it("should parse with Mi suffix", () => {
|
||||
expect(unitsToBytes("1234Mi")).toBe(1234 * (1024 ** 2));
|
||||
});
|
||||
|
||||
it("should parse with Mi suffix as the same as MiB", () => {
|
||||
expect(unitsToBytes("1234Mi")).toBe(unitsToBytes("1234MiB"));
|
||||
});
|
||||
|
||||
it("should parse with Gi suffix", () => {
|
||||
expect(unitsToBytes("1234Gi")).toBe(1234 * (1024 ** 3));
|
||||
});
|
||||
|
||||
it("should parse with Gi suffix as the same as GiB", () => {
|
||||
expect(unitsToBytes("1234Gi")).toBe(unitsToBytes("1234GiB"));
|
||||
});
|
||||
|
||||
it("should parse with Ti suffix", () => {
|
||||
expect(unitsToBytes("1234Ti")).toBe(1234 * (1024 ** 4));
|
||||
});
|
||||
|
||||
it("should parse with Ti suffix as the same as TiB", () => {
|
||||
expect(unitsToBytes("1234Ti")).toBe(unitsToBytes("1234TiB"));
|
||||
});
|
||||
|
||||
it("should parse with Pi suffix", () => {
|
||||
expect(unitsToBytes("1234Pi")).toBe(1234 * (1024 ** 5));
|
||||
});
|
||||
|
||||
it("should parse with Pi suffix as the same as PiB", () => {
|
||||
expect(unitsToBytes("1234Pi")).toBe(unitsToBytes("1234PiB"));
|
||||
});
|
||||
|
||||
it("given unrelated data, return NaN", () => {
|
||||
expect(unitsToBytes("I am not a number")).toBeNaN();
|
||||
});
|
||||
|
||||
it("given unrelated data, but has number, return that", () => {
|
||||
expect(unitsToBytes("I am not a number, but this is 0.1")).toBe(0.1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bytesToUnits", () => {
|
||||
it("should return N/A for invalid bytes", () => {
|
||||
expect(bytesToUnits(-1)).toBe("N/A");
|
||||
expect(bytesToUnits(Infinity)).toBe("N/A");
|
||||
expect(bytesToUnits(NaN)).toBe("N/A");
|
||||
});
|
||||
|
||||
it("given a number within the magnitude of 0..124, format with B", () => {
|
||||
expect(bytesToUnits(100)).toBe("100.0B");
|
||||
});
|
||||
|
||||
it("given a number within the magnitude of 1024..1024^2, format with KiB", () => {
|
||||
expect(bytesToUnits(1024)).toBe("1.0KiB");
|
||||
expect(bytesToUnits(2048)).toBe("2.0KiB");
|
||||
expect(bytesToUnits(1900)).toBe("1.9KiB");
|
||||
expect(bytesToUnits(50*1024 + 1)).toBe("50.0KiB");
|
||||
});
|
||||
|
||||
it("given a number within the magnitude of 1024^2..1024^3, format with MiB", () => {
|
||||
expect(bytesToUnits(1024**2)).toBe("1.0MiB");
|
||||
expect(bytesToUnits(2048**2)).toBe("4.0MiB");
|
||||
expect(bytesToUnits(1900 * 1024)).toBe("1.9MiB");
|
||||
expect(bytesToUnits(50*(1024 ** 2) + 1)).toBe("50.0MiB");
|
||||
});
|
||||
|
||||
it("given a number within the magnitude of 1024^3..1024^4, format with GiB", () => {
|
||||
expect(bytesToUnits(1024**3)).toBe("1.0GiB");
|
||||
expect(bytesToUnits(2048**3)).toBe("8.0GiB");
|
||||
expect(bytesToUnits(1900 * 1024 ** 2)).toBe("1.9GiB");
|
||||
expect(bytesToUnits(50*(1024 ** 3) + 1)).toBe("50.0GiB");
|
||||
});
|
||||
|
||||
it("given a number within the magnitude of 1024^4..1024^5, format with TiB", () => {
|
||||
expect(bytesToUnits(1024**4)).toBe("1.0TiB");
|
||||
expect(bytesToUnits(2048**4)).toBe("16.0TiB");
|
||||
expect(bytesToUnits(1900 * 1024 ** 3)).toBe("1.9TiB");
|
||||
expect(bytesToUnits(50*(1024 ** 4) + 1)).toBe("50.0TiB");
|
||||
});
|
||||
|
||||
it("given a number within the magnitude of 1024^5..1024^6, format with PiB", () => {
|
||||
expect(bytesToUnits(1024**5)).toBe("1.0PiB");
|
||||
expect(bytesToUnits(2048**5)).toBe("32.0PiB");
|
||||
expect(bytesToUnits(1900 * 1024 ** 4)).toBe("1.9PiB");
|
||||
expect(bytesToUnits(50*(1024 ** 5) + 1)).toBe("50.0PiB");
|
||||
});
|
||||
});
|
||||
|
||||
describe("bytesToUnits -> unitsToBytes", () => {
|
||||
it("given an input, round trip to the same value, given enough precision", () => {
|
||||
expect(unitsToBytes(bytesToUnits(123))).toBe(123);
|
||||
expect(unitsToBytes(bytesToUnits(1024**0 + 1, { precision: 2 }))).toBe(1024**0 + 1);
|
||||
expect(unitsToBytes(bytesToUnits(1024**1 + 2, { precision: 3 }))).toBe(1024**1 + 2);
|
||||
expect(unitsToBytes(bytesToUnits(1024**2 + 3, { precision: 6 }))).toBe(1024**2 + 3);
|
||||
expect(unitsToBytes(bytesToUnits(1024**3 + 4, { precision: 9 }))).toBe(1024**3 + 4);
|
||||
expect(unitsToBytes(bytesToUnits(1024**4 + 5, { precision: 12 }))).toBe(1024**4 + 5);
|
||||
expect(unitsToBytes(bytesToUnits(1024**5 + 6, { precision: 16 }))).toBe(1024**5 + 6);
|
||||
expect(unitsToBytes(bytesToUnits(1024**6 + 7, { precision: 20 }))).toBe(1024**6 + 7);
|
||||
});
|
||||
|
||||
it("given an invalid input, round trips to NaN", () => {
|
||||
expect(unitsToBytes(bytesToUnits(-1))).toBeNaN();
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import moment from "moment";
|
||||
import { formatDuration } from "../formatDuration";
|
||||
|
||||
const second = 1000;
|
||||
const minute = 60 * second;
|
||||
const hour = 60 * minute;
|
||||
const day = 24 * hour;
|
||||
const year = 365 * day;
|
||||
|
||||
describe("human format durations", () => {
|
||||
test("small duration should output something", () => {
|
||||
expect(formatDuration(1)).toBe("0s");
|
||||
expect(formatDuration(3)).toBe("0s");
|
||||
});
|
||||
|
||||
test("returns seconds for duration under 1 min", () => {
|
||||
const res = formatDuration(8 * second);
|
||||
|
||||
expect(res).toBe("8s");
|
||||
});
|
||||
|
||||
test("zero duration should output something", () => {
|
||||
expect(formatDuration(0)).toBe("0s");
|
||||
});
|
||||
|
||||
describe("when compact is true", () => {
|
||||
|
||||
test("duration under 3 hours return minutes", () => {
|
||||
const res = formatDuration(1 * hour + 35 * minute);
|
||||
|
||||
expect(res).toBe("95m");
|
||||
});
|
||||
|
||||
test("duration under 8 hours return hours and minutes", () => {
|
||||
const res = formatDuration(6 * hour + 15 * minute + 20 * second);
|
||||
|
||||
expect(res).toBe("6h15m");
|
||||
});
|
||||
|
||||
test("duration under 48 hours return hours", () => {
|
||||
const res = formatDuration(1 * day + 4 * hour + 15 * minute);
|
||||
|
||||
expect(res).toBe("28h");
|
||||
});
|
||||
|
||||
test("duration under 2 years return days", () => {
|
||||
const res = formatDuration(400 * day + 4 * hour + 15 * minute);
|
||||
|
||||
expect(res).toBe("400d");
|
||||
});
|
||||
|
||||
test("durations less than 8 years returns years and days", () => {
|
||||
const timeValue = new Date(2020, 0, 10, 12, 0, 0, 0).getTime() - new Date(2018, 0, 4, 12, 0, 0, 0).getTime();
|
||||
|
||||
const res = formatDuration(timeValue);
|
||||
|
||||
expect(res).toBe("2y5d");
|
||||
});
|
||||
|
||||
test("durations more than 8 years returns years", () => {
|
||||
const timeValue = Date.now() - new Date(moment().subtract(9, "years").subtract(5, "days").toDate()).getTime();
|
||||
|
||||
const res = formatDuration(timeValue);
|
||||
|
||||
expect(res).toBe("9y");
|
||||
});
|
||||
|
||||
test("durations more than 8 years returns years", () => {
|
||||
const res = formatDuration(10 * year + 25 * day);
|
||||
|
||||
expect(res).toBe("10y");
|
||||
});
|
||||
test("durations shouldn't include zero magnitude parts", () => {
|
||||
const zeroSeconds = formatDuration(8 * minute);
|
||||
|
||||
expect(zeroSeconds).toBe("8m");
|
||||
|
||||
const zeroMinutes = formatDuration(8 * hour + 15 * minute);
|
||||
|
||||
expect(zeroMinutes).toBe("8h");
|
||||
|
||||
const zeroHours = formatDuration(6 * day + 2 * minute);
|
||||
|
||||
expect(zeroHours).toBe("6d");
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,512 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { HashSet, ObservableHashSet } from "../hash-set";
|
||||
|
||||
describe("ObservableHashSet<T>", () => {
|
||||
it("should not throw on creation", () => {
|
||||
expect(() => new ObservableHashSet<{ a: number }>([], item => item.a.toString())).not.toThrowError();
|
||||
});
|
||||
|
||||
it("should be initializable", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.size).toBe(4);
|
||||
});
|
||||
|
||||
it("has should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
expect(res.has({ a: 5 })).toBe(false);
|
||||
});
|
||||
|
||||
it("forEach should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
let a = 1;
|
||||
|
||||
res.forEach((item) => {
|
||||
expect(item.a).toEqual(a++);
|
||||
});
|
||||
});
|
||||
|
||||
it("delete should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
expect(res.delete({ a: 1 })).toBe(true);
|
||||
expect(res.has({ a: 1 })).toBe(false);
|
||||
|
||||
expect(res.has({ a: 5 })).toBe(false);
|
||||
expect(res.delete({ a: 5 })).toBe(false);
|
||||
expect(res.has({ a: 5 })).toBe(false);
|
||||
});
|
||||
|
||||
it("toggle should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
res.toggle({ a: 1 });
|
||||
expect(res.has({ a: 1 })).toBe(false);
|
||||
|
||||
expect(res.has({ a: 6 })).toBe(false);
|
||||
res.toggle({ a: 6 });
|
||||
expect(res.has({ a: 6 })).toBe(true);
|
||||
});
|
||||
|
||||
it("add should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 6 })).toBe(false);
|
||||
res.add({ a: 6 });
|
||||
expect(res.has({ a: 6 })).toBe(true);
|
||||
});
|
||||
|
||||
it("add should treat the hash to be the same as equality", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1, foobar: "hello" },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
res.add({ a: 1, foobar: "goodbye" });
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
});
|
||||
|
||||
it("clear should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.size).toBe(4);
|
||||
res.clear();
|
||||
expect(res.size).toBe(0);
|
||||
});
|
||||
|
||||
it("replace should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.size).toBe(4);
|
||||
res.replace([{ a: 13 }]);
|
||||
expect(res.size).toBe(1);
|
||||
expect(res.has({ a: 1 })).toBe(false);
|
||||
expect(res.has({ a: 13 })).toBe(true);
|
||||
});
|
||||
|
||||
it("toJSON should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.toJSON()).toStrictEqual([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("values should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
const iter = res.values();
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 1 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 2 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 3 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 4 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: undefined,
|
||||
done: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("keys should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
const iter = res.keys();
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 1 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 2 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 3 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 4 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: undefined,
|
||||
done: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("entries should work as expected", () => {
|
||||
const res = new ObservableHashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
const iter = res.entries();
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: [{ a: 1 }, { a: 1 }],
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: [{ a: 2 }, { a: 2 }],
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: [{ a: 3 }, { a: 3 }],
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: [{ a: 4 }, { a: 4 }],
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: undefined,
|
||||
done: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("HashSet<T>", () => {
|
||||
it("should not throw on creation", () => {
|
||||
expect(() => new HashSet<{ a: number }>([], item => item.a.toString())).not.toThrowError();
|
||||
});
|
||||
|
||||
it("should be initializable", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.size).toBe(4);
|
||||
});
|
||||
|
||||
it("has should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
expect(res.has({ a: 5 })).toBe(false);
|
||||
});
|
||||
|
||||
it("forEach should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
let a = 1;
|
||||
|
||||
res.forEach((item) => {
|
||||
expect(item.a).toEqual(a++);
|
||||
});
|
||||
});
|
||||
|
||||
it("delete should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
expect(res.delete({ a: 1 })).toBe(true);
|
||||
expect(res.has({ a: 1 })).toBe(false);
|
||||
|
||||
expect(res.has({ a: 5 })).toBe(false);
|
||||
expect(res.delete({ a: 5 })).toBe(false);
|
||||
expect(res.has({ a: 5 })).toBe(false);
|
||||
});
|
||||
|
||||
it("toggle should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
res.toggle({ a: 1 });
|
||||
expect(res.has({ a: 1 })).toBe(false);
|
||||
|
||||
expect(res.has({ a: 6 })).toBe(false);
|
||||
res.toggle({ a: 6 });
|
||||
expect(res.has({ a: 6 })).toBe(true);
|
||||
});
|
||||
|
||||
it("add should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 6 })).toBe(false);
|
||||
res.add({ a: 6 });
|
||||
expect(res.has({ a: 6 })).toBe(true);
|
||||
});
|
||||
|
||||
it("add should treat the hash to be the same as equality", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1, foobar: "hello" },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
res.add({ a: 1, foobar: "goodbye" });
|
||||
expect(res.has({ a: 1 })).toBe(true);
|
||||
});
|
||||
|
||||
it("clear should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.size).toBe(4);
|
||||
res.clear();
|
||||
expect(res.size).toBe(0);
|
||||
});
|
||||
|
||||
it("replace should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.size).toBe(4);
|
||||
res.replace([{ a: 13 }]);
|
||||
expect(res.size).toBe(1);
|
||||
expect(res.has({ a: 1 })).toBe(false);
|
||||
expect(res.has({ a: 13 })).toBe(true);
|
||||
});
|
||||
|
||||
it("toJSON should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
|
||||
expect(res.toJSON()).toStrictEqual([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("values should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
const iter = res.values();
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 1 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 2 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 3 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 4 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: undefined,
|
||||
done: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("keys should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
const iter = res.keys();
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 1 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 2 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 3 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: { a: 4 },
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: undefined,
|
||||
done: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("entries should work as expected", () => {
|
||||
const res = new HashSet([
|
||||
{ a: 1 },
|
||||
{ a: 2 },
|
||||
{ a: 3 },
|
||||
{ a: 4 },
|
||||
], item => item.a.toString());
|
||||
const iter = res.entries();
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: [{ a: 1 }, { a: 1 }],
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: [{ a: 2 }, { a: 2 }],
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: [{ a: 3 }, { a: 3 }],
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: [{ a: 4 }, { a: 4 }],
|
||||
done: false,
|
||||
});
|
||||
|
||||
expect(iter.next()).toStrictEqual({
|
||||
value: undefined,
|
||||
done: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { join, nth, reduce, concat } from "../iter";
|
||||
|
||||
describe("iter", () => {
|
||||
describe("reduce", () => {
|
||||
it("can reduce a value", () => {
|
||||
expect(reduce([1, 2, 3], (acc: number[], current: number) => [current, ...acc], [0])).toEqual([3, 2, 1, 0]);
|
||||
});
|
||||
|
||||
it("can reduce an empty iterable", () => {
|
||||
expect(reduce([], (acc: number[], current: number) => [acc[0] + current], [])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("join", () => {
|
||||
it("should not prefix the output by the seperator", () => {
|
||||
expect(join(["a", "b", "c"].values(), " ")).toBe("a b c");
|
||||
});
|
||||
|
||||
it("should return empty string if iterator is empty", () => {
|
||||
expect(join([].values(), " ")).toBe("");
|
||||
});
|
||||
|
||||
it("should return just first entry if iterator is of size 1", () => {
|
||||
expect(join(["d"].values(), " ")).toBe("d");
|
||||
});
|
||||
});
|
||||
|
||||
describe("nth", () => {
|
||||
it("should return undefined past the end of the iterator", () => {
|
||||
expect(nth(["a"], 123)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should by 0-indexing the index", () => {
|
||||
expect(nth(["a", "b"], 0)).toBe("a");
|
||||
});
|
||||
});
|
||||
|
||||
describe("concat", () => {
|
||||
it("should yield undefined for empty args", () => {
|
||||
const iter = concat();
|
||||
|
||||
expect(iter.next()).toEqual({ done: true });
|
||||
});
|
||||
|
||||
it("should yield undefined for only empty args", () => {
|
||||
const iter = concat([].values(), [].values(), [].values(), [].values());
|
||||
|
||||
expect(iter.next()).toEqual({ done: true });
|
||||
});
|
||||
|
||||
it("should yield all of the first and then all of the second", () => {
|
||||
const iter = concat([1, 2, 3].values(), [4, 5, 6].values());
|
||||
|
||||
expect(iter.next()).toEqual({ done: false, value: 1 });
|
||||
expect(iter.next()).toEqual({ done: false, value: 2 });
|
||||
expect(iter.next()).toEqual({ done: false, value: 3 });
|
||||
expect(iter.next()).toEqual({ done: false, value: 4 });
|
||||
expect(iter.next()).toEqual({ done: false, value: 5 });
|
||||
expect(iter.next()).toEqual({ done: false, value: 6 });
|
||||
expect(iter.next()).toEqual({ done: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { nFircate } from "../n-fircate";
|
||||
|
||||
describe("nFircate", () => {
|
||||
it("should produce an empty array if no parts are provided", () => {
|
||||
expect(nFircate([{ a: 1 }, { a: 2 }], "a", []).length).toBe(0);
|
||||
});
|
||||
|
||||
it("should ignore non-matching parts", () => {
|
||||
const res = nFircate([{ a: 1 }, { a: 2 }], "a", [1]);
|
||||
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0].length).toBe(1);
|
||||
});
|
||||
|
||||
it("should include all matching parts in each type", () => {
|
||||
const res = nFircate([{ a: 1, b: "a" }, { a: 2, b: "b" }, { a: 1, b: "c" }], "a", [1, 2]);
|
||||
|
||||
expect(res.length).toBe(2);
|
||||
expect(res[0].length).toBe(2);
|
||||
expect(res[0][0].b).toBe("a");
|
||||
expect(res[0][1].b).toBe("c");
|
||||
expect(res[1].length).toBe(1);
|
||||
expect(res[1][0].b).toBe("b");
|
||||
});
|
||||
|
||||
it("should throw a type error if the same part is provided more than once", () => {
|
||||
try {
|
||||
nFircate([{ a: 1, b: "a" }, { a: 2, b: "b" }, { a: 1, b: "c" }], "a", [1, 2, 1]);
|
||||
fail("Expected error");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(TypeError);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { bifurcateArray, splitArray } from "../splitArray";
|
||||
|
||||
describe("split array on element tests", () => {
|
||||
it("empty array", () => {
|
||||
expect(splitArray([], 10)).toStrictEqual([[], [], false]);
|
||||
});
|
||||
|
||||
it("one element, not in array", () => {
|
||||
expect(splitArray([1], 10)).toStrictEqual([[1], [], false]);
|
||||
});
|
||||
|
||||
it("ten elements, not in array", () => {
|
||||
expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10)).toStrictEqual([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [], false]);
|
||||
});
|
||||
|
||||
it("one elements, in array", () => {
|
||||
expect(splitArray([1], 1)).toStrictEqual([[], [], true]);
|
||||
});
|
||||
|
||||
it("ten elements, in front array", () => {
|
||||
expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0)).toStrictEqual([[], [1, 2, 3, 4, 5, 6, 7, 8, 9], true]);
|
||||
});
|
||||
|
||||
it("ten elements, in middle array", () => {
|
||||
expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4)).toStrictEqual([[0, 1, 2, 3], [5, 6, 7, 8, 9], true]);
|
||||
});
|
||||
|
||||
it("ten elements, in end array", () => {
|
||||
expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9)).toStrictEqual([[0, 1, 2, 3, 4, 5, 6, 7, 8], [], true]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bifurcateArray", () => {
|
||||
it("should return tuple of empty arrays from empty array", () => {
|
||||
const [left, right] = bifurcateArray([], () => true);
|
||||
|
||||
expect(left).toStrictEqual([]);
|
||||
expect(right).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("should return all true condition returning items in the right array", () => {
|
||||
const [left, right] = bifurcateArray([1, 2, 3], () => true);
|
||||
|
||||
expect(left).toStrictEqual([]);
|
||||
expect(right).toStrictEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it("should return all false condition returning items in the right array", () => {
|
||||
const [left, right] = bifurcateArray([1, 2, 3], () => false);
|
||||
|
||||
expect(left).toStrictEqual([1, 2, 3]);
|
||||
expect(right).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("should split array as specified", () => {
|
||||
const [left, right] = bifurcateArray([1, 2, 3], (i) => Boolean(i % 2));
|
||||
|
||||
expect(left).toStrictEqual([2]);
|
||||
expect(right).toStrictEqual([1, 3]);
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { tuple } from "../../utils";
|
||||
|
||||
describe("tuple tests", () => {
|
||||
describe("zip()", () => {
|
||||
it("should yield 0 times and return 1 tuple of empty arrays when given empty array", () => {
|
||||
expect(tuple.zip([]).next()).toEqual({
|
||||
done: true,
|
||||
value: [[]],
|
||||
});
|
||||
});
|
||||
|
||||
it("should yield 1 times and return 2 tuple of empty arrays when given one element array tuples", () => {
|
||||
const i = tuple.zip([1], [2]);
|
||||
|
||||
expect(i.next()).toEqual({
|
||||
done: false,
|
||||
value: [1, 2],
|
||||
});
|
||||
expect(i.next()).toEqual({
|
||||
done: true,
|
||||
value: [[], []],
|
||||
});
|
||||
});
|
||||
|
||||
it("should yield 1 times and return 2 tuple of partial arrays when given one element array tuples", () => {
|
||||
const i = tuple.zip([1], [2, 3]);
|
||||
|
||||
expect(i.next()).toEqual({
|
||||
done: false,
|
||||
value: [1, 2],
|
||||
});
|
||||
expect(i.next()).toEqual({
|
||||
done: true,
|
||||
value: [[], [3]],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import { unionPATHs } from "../union-env-path";
|
||||
|
||||
describe("unionPATHs", () => {
|
||||
it("return the same path if given only one with no double delimiters", () => {
|
||||
expect(unionPATHs(`/bin/bar${path.delimiter}/usr/bin`)).toBe(`/bin/bar${path.delimiter}/usr/bin`);
|
||||
});
|
||||
|
||||
it("return equivalent path if given only one with no double delimiters", () => {
|
||||
expect(unionPATHs(`/bin/bar${path.delimiter}${path.delimiter}/usr/bin`)).toBe(`/bin/bar${path.delimiter}/usr/bin`);
|
||||
});
|
||||
|
||||
it("should remove duplicate entries, appending non duplicates in order received", () => {
|
||||
expect(unionPATHs(
|
||||
`/bin/bar${path.delimiter}/usr/bin`,
|
||||
`/bin/bar${path.delimiter}/usr/lens/bat`,
|
||||
)).toBe(`/bin/bar${path.delimiter}/usr/bin${path.delimiter}/usr/lens/bat`);
|
||||
});
|
||||
|
||||
it("should remove duplicate entries, appending non duplicates in order received, 3", () => {
|
||||
expect(unionPATHs(
|
||||
`/bin/bar${path.delimiter}/usr/bin`,
|
||||
`/bin/bar${path.delimiter}/usr/lens/bat`,
|
||||
`/usr/local/lens${path.delimiter}/usr/bin`,
|
||||
)).toBe(`/bin/bar${path.delimiter}/usr/bin${path.delimiter}/usr/lens/bat${path.delimiter}/usr/local/lens`);
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import AbortController from "abort-controller";
|
||||
|
||||
/**
|
||||
* This is like an `AbortController` but will also abort if the parent aborts,
|
||||
* but won't make the parent abort if this aborts (single direction)
|
||||
*/
|
||||
export class WrappedAbortController extends AbortController {
|
||||
constructor(parent?: AbortController | undefined) {
|
||||
super();
|
||||
|
||||
parent?.signal.addEventListener("abort", () => {
|
||||
this.abort();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function setTimeoutFor(controller: AbortController, timeout: number): void {
|
||||
const handle = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
controller.signal.addEventListener("abort", () => clearTimeout(handle));
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A inference typed version of `Array(length).fill(value)`
|
||||
* @param length The number of entries
|
||||
* @param value The value of each of the indices
|
||||
*/
|
||||
export function filled<T>(length: number, value: T): T[] {
|
||||
return Array(length).fill(value);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export type AsyncResult<Response, Error = string> =
|
||||
| (
|
||||
Response extends void
|
||||
? { callWasSuccessful: true; response?: undefined }
|
||||
: { callWasSuccessful: true; response: Response }
|
||||
)
|
||||
| { callWasSuccessful: false; error: Error };
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Options } from "auto-bind";
|
||||
import autoBindClass from "auto-bind";
|
||||
import autoBindReactClass from "auto-bind/react";
|
||||
import React from "react";
|
||||
|
||||
// Automatically bind methods to their class instance
|
||||
export function autoBind<T extends object>(obj: T, opts?: Options): T {
|
||||
if (obj instanceof React.Component) {
|
||||
return autoBindReactClass(obj, opts);
|
||||
}
|
||||
|
||||
return autoBindClass(obj, opts);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { AsyncResult } from "./async-result";
|
||||
import { delay } from "./delay";
|
||||
import { noop } from "./noop";
|
||||
|
||||
/**
|
||||
* @param error The error that resulted in the failure
|
||||
* @param attempt The 1-index attempt count
|
||||
*/
|
||||
export type OnIntermediateError<E> = (error: E, attempt: number) => void;
|
||||
|
||||
export interface BackoffCallerOptions<E> {
|
||||
/**
|
||||
* Called when an attempt fails
|
||||
*/
|
||||
onIntermediateError?: OnIntermediateError<E>;
|
||||
|
||||
/**
|
||||
* @default 5
|
||||
*/
|
||||
maxAttempts?: number;
|
||||
|
||||
/**
|
||||
* In milliseconds
|
||||
* @default 1000
|
||||
*/
|
||||
initialTimeout?: number;
|
||||
|
||||
/**
|
||||
* @default 2
|
||||
*/
|
||||
scaleFactor?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `fn` once and then again (with exponential delay between each attempt) up to `options.maxAttempts` times.
|
||||
* @param fn The function to repeatedly attempt
|
||||
* @returns The first success or the last failure
|
||||
*/
|
||||
export const backoffCaller = async <T, E, R extends AsyncResult<T, E>>(fn: () => Promise<R>, options?: BackoffCallerOptions<E>): Promise<R> => {
|
||||
const {
|
||||
initialTimeout = 1000,
|
||||
maxAttempts = 5,
|
||||
onIntermediateError = noop as OnIntermediateError<E>,
|
||||
scaleFactor = 2,
|
||||
} = options ?? {};
|
||||
|
||||
let timeout = initialTimeout;
|
||||
let attempt = 0;
|
||||
let result: R;
|
||||
|
||||
do {
|
||||
result = await fn();
|
||||
|
||||
if (result.callWasSuccessful) {
|
||||
return result;
|
||||
}
|
||||
|
||||
onIntermediateError(result.error, attempt + 1);
|
||||
|
||||
await delay(timeout);
|
||||
timeout *= scaleFactor;
|
||||
} while (attempt += 1, attempt < maxAttempts);
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Encode/decode utf-8 base64 string
|
||||
import * as Base64 from "crypto-js/enc-base64";
|
||||
import * as Utf8 from "crypto-js/enc-utf8";
|
||||
|
||||
/**
|
||||
* Computes utf-8 from base64
|
||||
* @param data A Base64 encoded string
|
||||
* @returns The original utf-8 string
|
||||
*/
|
||||
export function decode(data: string): string {
|
||||
return Base64.parse(data).toString(Utf8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes base64 from utf-8
|
||||
* @param data A normal string
|
||||
* @returns A base64 encoded version
|
||||
*/
|
||||
export function encode(data: string): string {
|
||||
return Utf8.parse(data).toString(Base64);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { compile } from "path-to-regexp";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { isDefined } from "./type-narrowing";
|
||||
|
||||
export interface UrlRouteProps extends RouteProps {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface URLParams<P extends object = {}, Q extends object = {}> {
|
||||
params?: P;
|
||||
query?: Q;
|
||||
fragment?: string;
|
||||
}
|
||||
|
||||
export function buildURL<P extends object = {}, Q extends object = {}>(path: string, { params, query, fragment }: URLParams<P, Q> = {}) {
|
||||
const pathBuilder = compile(String(path));
|
||||
|
||||
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : "";
|
||||
const parts = [
|
||||
pathBuilder(params),
|
||||
queryParams && `?${queryParams}`,
|
||||
fragment && `#${fragment}`,
|
||||
];
|
||||
|
||||
return parts.filter(isDefined).join("");
|
||||
}
|
||||
|
||||
export function buildURLPositional<P extends object = {}, Q extends object = {}>(path: string) {
|
||||
return function (params?: P, query?: Q, fragment?: string): string {
|
||||
return buildURL(path, { params, query, fragment });
|
||||
};
|
||||
}
|
||||
|
||||
export type UrlParamsFor<Pathname extends string> =
|
||||
Pathname extends `${string}/:${infer A}?/${infer Tail}`
|
||||
? Partial<Record<A, string>> & UrlParamsFor<`/${Tail}`>
|
||||
: Pathname extends `${string}/:${infer A}/${infer Tail}`
|
||||
? Record<A, string> & UrlParamsFor<`/${Tail}`>
|
||||
: Pathname extends `${string}/:${infer A}?`
|
||||
? Partial<Record<A, string>>
|
||||
: Pathname extends `${string}/:${infer A}`
|
||||
? Record<A, string>
|
||||
: {};
|
||||
|
||||
export interface UrlBuilder<Pathname extends string> {
|
||||
compile(params: UrlParamsFor<Pathname>, query?: object, fragment?: string): string;
|
||||
}
|
||||
|
||||
export function urlBuilderFor<Pathname extends string>(pathname: Pathname): UrlBuilder<Pathname> {
|
||||
return {
|
||||
compile: buildURLPositional(pathname),
|
||||
};
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Convert object's keys to camelCase format
|
||||
import { camelCase } from "lodash";
|
||||
import type { SingleOrMany } from "./types";
|
||||
import { isObject, isString } from "./type-narrowing";
|
||||
import * as object from "./objects";
|
||||
|
||||
export function toCamelCase<T extends Record<string, unknown>[]>(obj: T): T;
|
||||
export function toCamelCase<T extends Record<string, unknown>>(obj: T): T;
|
||||
|
||||
export function toCamelCase(obj: SingleOrMany<Record<string, unknown> | unknown>): SingleOrMany<Record<string, unknown> | unknown> {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(toCamelCase);
|
||||
}
|
||||
|
||||
if (isObject(obj)) {
|
||||
return object.fromEntries(
|
||||
object.entries(obj)
|
||||
.filter((pair): pair is [string, unknown] => isString(pair[0]))
|
||||
.map(([key, value]) => [camelCase(key), isObject(value) ? toCamelCase(value) : value]),
|
||||
);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import { requestFromChannelInjectionToken } from "./request-from-channel-injecti
|
||||
import type { RequestChannel } from "./request-channel-listener-injection-token";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import { getPromiseStatus } from "../../test-utils/get-promise-status";
|
||||
import { getPromiseStatus } from "@k8slens/test-utils";
|
||||
import { runInAction } from "mobx";
|
||||
import type { RequestChannelHandler } from "../../../main/utils/channel/channel-listeners/listener-tokens";
|
||||
import {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { Disposer } from "../disposer";
|
||||
import type { Disposer } from "@k8slens/utilities";
|
||||
import type { MessageChannel, MessageChannelListener } from "./message-channel-listener-injection-token";
|
||||
|
||||
export type EnlistMessageChannelListener = (listener: MessageChannelListener<MessageChannel<unknown>>) => Disposer;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { getStartableStoppable } from "../get-startable-stoppable";
|
||||
import { disposer } from "../index";
|
||||
import { messageChannelListenerInjectionToken } from "./message-channel-listener-injection-token";
|
||||
import { enlistMessageChannelListenerInjectionToken } from "./enlist-message-channel-listener-injection-token";
|
||||
import { disposer } from "@k8slens/utilities";
|
||||
|
||||
const listeningOnMessageChannelsInjectable = getInjectable({
|
||||
id: "listening-on-message-channels",
|
||||
|
||||
@@ -6,8 +6,8 @@ import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { RequestChannel } from "./request-channel-listener-injection-token";
|
||||
|
||||
export interface RequestFromChannel {
|
||||
<Request, Response>(channel: RequestChannel<Request, Response>, request: Request): Promise<Response>;
|
||||
<Response>(channel: RequestChannel<void, Response>): Promise<Response>;
|
||||
<Request, Response>(channel: RequestChannel<Request, Response>, request: Request): Promise<Awaited<Response>>;
|
||||
<Response>(channel: RequestChannel<void, Response>): Promise<Awaited<Response>>;
|
||||
}
|
||||
|
||||
export const requestFromChannelInjectionToken = getInjectionToken<RequestFromChannel>({
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { runInAction } from "mobx";
|
||||
import { inspect } from "util";
|
||||
import { isDefined } from "./type-narrowing";
|
||||
|
||||
/**
|
||||
* Get the value behind `key`. If it was not present, first insert `value`
|
||||
* @param map The map to interact with
|
||||
* @param key The key to insert into the map with
|
||||
* @param value The value to optional add to the map
|
||||
* @returns The value in the map
|
||||
*/
|
||||
export function getOrInsert<K, V>(map: Map<K, V>, key: K, value: V): V {
|
||||
if (!map.has(key)) {
|
||||
map.set(key, value);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return map.get(key)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates map and returns the value that was just inserted
|
||||
*/
|
||||
export function put<K, V>(map: Map<K, V>, key: K, value: V): V {
|
||||
map.set(key, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `getOrInsert` but specifically for when `V` is `Map<MK, MV>` so that
|
||||
* the typings are inferred correctly.
|
||||
*/
|
||||
export function getOrInsertMap<K, MK, MV>(map: Map<K, Map<MK, MV>>, key: K): Map<MK, MV> {
|
||||
return getOrInsert(map, key, new Map<MK, MV>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `getOrInsert` but specifically for when `V` is `Set<any>` so that
|
||||
* the typings are inferred.
|
||||
*/
|
||||
export function getOrInsertSet<K, SK>(map: Map<K, Set<SK>>, key: K): Set<SK> {
|
||||
return getOrInsert(map, key, new Set<SK>());
|
||||
}
|
||||
|
||||
/**
|
||||
* A currying version of {@link getOrInsertSet}
|
||||
*/
|
||||
export function getOrInsertSetFor<K, SK>(map: Map<K, Set<SK>>): (key: K) => Set<SK> {
|
||||
return (key) => getOrInsertSet(map, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `getOrInsert` but with delayed creation of the item. Which is useful
|
||||
* if it is very expensive to create the initial value.
|
||||
*/
|
||||
export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, builder: () => V): V;
|
||||
export function getOrInsertWith<K extends object, V>(map: Map<K, V> | WeakMap<K, V>, key: K, builder: () => V): V;
|
||||
|
||||
export function getOrInsertWith<K extends object, V>(map: Map<K, V> | WeakMap<K, V>, key: K, builder: () => V): V {
|
||||
if (!map.has(key)) {
|
||||
map.set(key, builder());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return map.get(key)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link getOrInsertWith} but the builder is async and will be awaited before inserting into the map
|
||||
*/
|
||||
export async function getOrInsertWithAsync<K, V>(map: Map<K, V>, key: K, asyncBuilder: () => Promise<V>): Promise<V> {
|
||||
if (!map.has(key)) {
|
||||
map.set(key, await asyncBuilder());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return map.get(key)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value associated with `key` iff there was not a previous value
|
||||
* @param map The map to interact with
|
||||
* @throws if `key` already in map
|
||||
* @returns `this` so that `strictSet` can be chained
|
||||
*/
|
||||
export function strictSet<K, V>(map: Map<K, V>, key: K, val: V): typeof map {
|
||||
if (map.has(key)) {
|
||||
throw new TypeError(`Map already contains key: ${inspect(key)}`);
|
||||
}
|
||||
|
||||
return map.set(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value associated with `key`
|
||||
* @param map The map to interact with
|
||||
* @throws if `key` did not a value associated with it
|
||||
*/
|
||||
export function strictGet<K, V>(map: Map<K, V>, key: K): V {
|
||||
if (!map.has(key)) {
|
||||
throw new TypeError(`Map does not contains key: ${inspect(key)}`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return map.get(key)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `key` is in `set`, remove it otherwise add it.
|
||||
* @param set The set to manipulate
|
||||
* @param key The key to toggle the "is in"-ness of
|
||||
*/
|
||||
export function toggle<K>(set: Set<K>, key: K): void {
|
||||
runInAction(() => {
|
||||
// Returns true if value was already in Set; otherwise false.
|
||||
if (!set.delete(key)) {
|
||||
set.add(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to also check for defined-ness
|
||||
*/
|
||||
export function includes<T>(src: T[], value: T | null | undefined): boolean {
|
||||
return isDefined(value) && src.includes(value);
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { isBoolean } from "@k8slens/utilities";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { isBoolean } from "../../type-narrowing";
|
||||
|
||||
export interface Showable {
|
||||
readonly isShown: IComputedValue<boolean> | boolean;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed } from "mobx";
|
||||
|
||||
export const computedOr = (...values: IComputedValue<boolean>[]) => computed((
|
||||
() => values.some(value => value.get())
|
||||
));
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { TypedRegEx } from "typed-regex";
|
||||
|
||||
// Helper to convert CPU K8S units to numbers
|
||||
|
||||
const unitConverters = new Map([
|
||||
["n", 1000 ** -3],
|
||||
["u", 1000 ** -2],
|
||||
["m", 1000 ** -1], // milli
|
||||
["", 1000 ** 0], // no units
|
||||
["k", 1000 ** 1],
|
||||
["M", 1000 ** 2],
|
||||
["G", 1000 ** 3],
|
||||
["P", 1000 ** 4],
|
||||
["T", 1000 ** 5],
|
||||
["E", 1000 ** 6],
|
||||
]);
|
||||
|
||||
const cpuUnitsRegex = TypedRegEx("^(?<digits>[+-]?[0-9.]+(e[-+]?[0-9]+)?)(?<unit>[EinumkKMGTP]*)$");
|
||||
|
||||
export function cpuUnitsToNumber(value: string) {
|
||||
const match = cpuUnitsRegex.captures(value);
|
||||
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { digits = "", unit } = match;
|
||||
const conversion = unitConverters.get(unit);
|
||||
|
||||
if (conversion === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return parseFloat(digits) * conversion;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import assert from "assert";
|
||||
import * as iter from "./iter";
|
||||
|
||||
// Helper to convert memory from units Ki, Mi, Gi, Ti, Pi to bytes and vise versa
|
||||
|
||||
const baseMagnitude = 1024;
|
||||
const maxMagnitude = ["PiB", baseMagnitude ** 5] as const;
|
||||
const magnitudes = new Map([
|
||||
["B", 1] as const,
|
||||
["KiB", baseMagnitude ** 1] as const,
|
||||
["MiB", baseMagnitude ** 2] as const,
|
||||
["GiB", baseMagnitude ** 3] as const,
|
||||
["TiB", baseMagnitude ** 4] as const,
|
||||
maxMagnitude,
|
||||
]);
|
||||
const unitRegex = /(?<value>[0-9]+(\.[0-9]*)?)(?<suffix>(B|[KMGTP]iB?))?/;
|
||||
|
||||
type BinaryUnit = typeof magnitudes extends Map<infer Key, any> ? Key : never;
|
||||
|
||||
export function unitsToBytes(value: string): number {
|
||||
const unitsMatch = value.match(unitRegex);
|
||||
|
||||
if (!unitsMatch?.groups) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const parsedValue = parseFloat(unitsMatch.groups.value);
|
||||
|
||||
if (!unitsMatch.groups?.suffix) {
|
||||
return parsedValue;
|
||||
}
|
||||
|
||||
const magnitude = magnitudes.get(unitsMatch.groups.suffix as BinaryUnit)
|
||||
?? magnitudes.get(`${unitsMatch.groups.suffix}B` as BinaryUnit);
|
||||
|
||||
assert(magnitude, "UnitRegex is wrong some how");
|
||||
|
||||
return parseInt((parsedValue * magnitude).toFixed(1));
|
||||
}
|
||||
|
||||
export interface BytesToUnitesOptions {
|
||||
/**
|
||||
* The number of decimal places. MUST be an integer. MUST be in the range [0, 20].
|
||||
* @default 1
|
||||
*/
|
||||
precision?: number;
|
||||
}
|
||||
|
||||
export function bytesToUnits(bytes: number, { precision = 1 }: BytesToUnitesOptions = {}): string {
|
||||
if (bytes <= 0 || isNaN(bytes) || !isFinite(bytes)) {
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
const index = Math.floor(Math.log(bytes) / Math.log(baseMagnitude));
|
||||
const [suffix, magnitude] = iter.nth(magnitudes.entries(), index) ?? maxMagnitude;
|
||||
|
||||
return `${(bytes / magnitude).toFixed(precision)}${suffix}`;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Debouncing promise evaluation
|
||||
|
||||
export function debouncePromise<T, F extends any[]>(func: (...args: F) => T | Promise<T>, timeout = 0): (...args: F) => Promise<T> {
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
return (...params: F) => new Promise(resolve => {
|
||||
clearTimeout(timer);
|
||||
timer = global.setTimeout(() => resolve(func(...params)), timeout);
|
||||
});
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type AbortController from "abort-controller";
|
||||
|
||||
/**
|
||||
* Return a promise that will be resolved after at least `timeout` ms have
|
||||
* passed. If `failFast` is provided then the promise is also resolved if it has
|
||||
* been aborted.
|
||||
* @param timeout The number of milliseconds before resolving
|
||||
* @param failFast An abort controller instance to cause the delay to short-circuit
|
||||
*/
|
||||
export function delay(timeout = 1000, failFast?: AbortController): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const timeoutId = setTimeout(resolve, timeout);
|
||||
|
||||
failFast?.signal.addEventListener("abort", () => {
|
||||
clearTimeout(timeoutId);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { SingleOrMany } from "./types";
|
||||
|
||||
|
||||
|
||||
export interface Disposer {
|
||||
(): void;
|
||||
}
|
||||
|
||||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface ExtendableDisposer extends Disposer {
|
||||
push(...vals: (Disposer | ExtendableDisposer | Disposable)[]): void;
|
||||
}
|
||||
|
||||
export function disposer(...items: SingleOrMany<Disposer | Disposable | undefined | null>[]): ExtendableDisposer {
|
||||
return Object.assign(() => {
|
||||
for (const item of items.flat()) {
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof item === "function") {
|
||||
item();
|
||||
} else {
|
||||
item.dispose();
|
||||
}
|
||||
}
|
||||
items.length = 0;
|
||||
}, {
|
||||
push: (...newItems) => items.push(...newItems),
|
||||
} as Pick<ExtendableDisposer, "push">);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Helper to sanitize / escape special chars for passing to RegExp-constructor
|
||||
|
||||
export function escapeRegExp(str: string) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import moment from "moment";
|
||||
|
||||
/**
|
||||
* This function formats durations in a more human readable form.
|
||||
* @param timeValue the duration in milliseconds to format
|
||||
*/
|
||||
export function formatDuration(timeValue: number, compact = true) {
|
||||
const duration = moment.duration(timeValue, "milliseconds");
|
||||
const seconds = Math.floor(duration.asSeconds());
|
||||
const separator = compact ? "": " ";
|
||||
|
||||
if (seconds < 0) {
|
||||
return "0s";
|
||||
} else if (seconds < 60*2 ) {
|
||||
return `${seconds}s`;
|
||||
}
|
||||
|
||||
const minutes = Math.floor(duration.asMinutes());
|
||||
|
||||
if (minutes < 10) {
|
||||
const seconds = duration.seconds();
|
||||
|
||||
return getMeaningfulValues([minutes, seconds], ["m", "s"], separator);
|
||||
} else if (minutes < 60 * 3) {
|
||||
if (!compact) {
|
||||
return getMeaningfulValues([minutes, duration.seconds()], ["m", "s"]);
|
||||
}
|
||||
|
||||
return `${minutes}m`;
|
||||
}
|
||||
|
||||
const hours = Math.floor(duration.asHours());
|
||||
|
||||
if(hours < 8) {
|
||||
const minutes = duration.minutes();
|
||||
|
||||
return getMeaningfulValues([hours, minutes], ["h", "m"], separator);
|
||||
} else if (hours < 48) {
|
||||
if (compact) {
|
||||
return `${hours}h`;
|
||||
} else {
|
||||
return getMeaningfulValues([hours, duration.minutes()], ["h", "m"]);
|
||||
}
|
||||
}
|
||||
|
||||
const days = Math.floor(duration.asDays());
|
||||
|
||||
if (days < 8) {
|
||||
const hours = duration.hours();
|
||||
|
||||
if (compact) {
|
||||
return getMeaningfulValues([days, hours], ["d", "h"], separator);
|
||||
} else {
|
||||
return getMeaningfulValues([days, hours, duration.minutes()], ["d", "h", "m"]);
|
||||
}
|
||||
}
|
||||
const years = Math.floor(duration.asYears());
|
||||
|
||||
if (years < 2) {
|
||||
if (compact) {
|
||||
return `${days}d`;
|
||||
} else {
|
||||
return getMeaningfulValues([days, duration.hours(), duration.minutes()], ["d", "h", "m"]);
|
||||
}
|
||||
} else if (years < 8) {
|
||||
const days = duration.days();
|
||||
|
||||
if (compact) {
|
||||
return getMeaningfulValues([years, days], ["y", "d"], separator);
|
||||
}
|
||||
}
|
||||
|
||||
if (compact) {
|
||||
return `${years}y`;
|
||||
}
|
||||
|
||||
return getMeaningfulValues([years, duration.days(), duration.hours(), duration.minutes()], ["y", "d", "h", "m"]);
|
||||
}
|
||||
|
||||
function getMeaningfulValues(values: number[], suffixes: string[], separator = " ") {
|
||||
return values
|
||||
.map((a, i): [number, string] => [a, suffixes[i]])
|
||||
.filter(([dur]) => dur > 0)
|
||||
.map(([dur, suf]) => dur + suf)
|
||||
.join(separator);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
||||
import getRandomIdInjectable from "./get-random-id.injectable";
|
||||
|
||||
export default getGlobalOverride(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Create random system name
|
||||
|
||||
export function getRandId({ prefix = "", suffix = "", sep = "_" } = {}) {
|
||||
const randId = () => Math.random().toString(16).slice(2);
|
||||
|
||||
return [prefix, randId(), suffix].filter(s => s).join(sep);
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { IInterceptable, IInterceptor, IListenable, ISetWillChange, ObservableMap } from "mobx";
|
||||
import { action, observable, ObservableSet } from "mobx";
|
||||
|
||||
export function makeIterableIterator<T>(iterator: Iterator<T>): IterableIterator<T> {
|
||||
(iterator as IterableIterator<T>)[Symbol.iterator] = () => iterator as IterableIterator<T>;
|
||||
|
||||
return iterator as IterableIterator<T>;
|
||||
}
|
||||
|
||||
export class HashSet<T> implements Set<T> {
|
||||
#hashmap: Map<string, T>;
|
||||
|
||||
constructor(initialValues: Iterable<T>, protected hasher: (item: T) => string) {
|
||||
this.#hashmap = new Map<string, T>(Array.from(initialValues, value => [this.hasher(value), value]));
|
||||
}
|
||||
|
||||
replace(other: ObservableHashSet<T> | ObservableSet<T> | Set<T> | readonly T[]): this {
|
||||
if (other === null || other === undefined) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!(Array.isArray(other) || other instanceof Set || other instanceof ObservableHashSet || other instanceof ObservableSet)) {
|
||||
throw new Error(`ObservableHashSet: Cannot initialize set from ${other}`);
|
||||
}
|
||||
|
||||
this.clear();
|
||||
|
||||
for (const value of other) {
|
||||
this.add(value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#hashmap.clear();
|
||||
}
|
||||
|
||||
add(value: T): this {
|
||||
this.#hashmap.set(this.hasher(value), value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toggle(value: T): void {
|
||||
const hash = this.hasher(value);
|
||||
|
||||
if (this.#hashmap.has(hash)) {
|
||||
this.#hashmap.delete(hash);
|
||||
} else {
|
||||
this.#hashmap.set(hash, value);
|
||||
}
|
||||
}
|
||||
|
||||
delete(value: T): boolean {
|
||||
return this.#hashmap.delete(this.hasher(value));
|
||||
}
|
||||
|
||||
forEach(callbackfn: (value: T, key: T, set: Set<T>) => void, thisArg?: any): void {
|
||||
this.#hashmap.forEach(value => callbackfn(value, value, thisArg ?? this));
|
||||
}
|
||||
|
||||
has(value: T): boolean {
|
||||
return this.#hashmap.has(this.hasher(value));
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.#hashmap.size;
|
||||
}
|
||||
|
||||
entries(): IterableIterator<[T, T]> {
|
||||
let nextIndex = 0;
|
||||
const keys = Array.from(this.keys());
|
||||
const values = Array.from(this.values());
|
||||
|
||||
return makeIterableIterator<[T, T]>({
|
||||
next() {
|
||||
const index = nextIndex++;
|
||||
|
||||
return index < values.length
|
||||
? { value: [keys[index], values[index]], done: false }
|
||||
: { done: true, value: undefined };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
keys(): IterableIterator<T> {
|
||||
return this.values();
|
||||
}
|
||||
|
||||
values(): IterableIterator<T> {
|
||||
let nextIndex = 0;
|
||||
const observableValues = Array.from(this.#hashmap.values());
|
||||
|
||||
return makeIterableIterator<T>({
|
||||
next: () => {
|
||||
return nextIndex < observableValues.length
|
||||
? { value: observableValues[nextIndex++], done: false }
|
||||
: { done: true, value: undefined };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<T> {
|
||||
return this.#hashmap.values();
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag](): string {
|
||||
return "Set";
|
||||
}
|
||||
|
||||
toJSON(): T[] {
|
||||
return Array.from(this);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return "[object Set]";
|
||||
}
|
||||
}
|
||||
|
||||
export class ObservableHashSet<T> implements Set<T>, IInterceptable<ISetWillChange>, IListenable {
|
||||
#hashmap: ObservableMap<string, T>;
|
||||
|
||||
get interceptors_(): IInterceptor<ISetWillChange<T>>[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
get changeListeners_(): Function[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
constructor(initialValues: Iterable<T>, protected hasher: (item: T) => string) {
|
||||
this.#hashmap = observable.map<string, T>(Array.from(initialValues, value => [this.hasher(value), value]), undefined);
|
||||
}
|
||||
|
||||
@action
|
||||
replace(other: ObservableHashSet<T> | ObservableSet<T> | Set<T> | readonly T[]): this {
|
||||
if (other === null || other === undefined) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!(Array.isArray(other) || other instanceof Set || other instanceof ObservableHashSet || other instanceof ObservableSet)) {
|
||||
throw new Error(`ObservableHashSet: Cannot initialize set from ${other}`);
|
||||
}
|
||||
|
||||
this.clear();
|
||||
|
||||
for (const value of other) {
|
||||
this.add(value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#hashmap.clear();
|
||||
}
|
||||
|
||||
add(value: T): this {
|
||||
this.#hashmap.set(this.hasher(value), value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@action
|
||||
toggle(value: T): void {
|
||||
const hash = this.hasher(value);
|
||||
|
||||
if (this.#hashmap.has(hash)) {
|
||||
this.#hashmap.delete(hash);
|
||||
} else {
|
||||
this.#hashmap.set(hash, value);
|
||||
}
|
||||
}
|
||||
|
||||
delete(value: T): boolean {
|
||||
return this.#hashmap.delete(this.hasher(value));
|
||||
}
|
||||
|
||||
forEach(callbackfn: (value: T, key: T, set: Set<T>) => void, thisArg?: any): void {
|
||||
this.#hashmap.forEach(value => callbackfn(value, value, thisArg ?? this));
|
||||
}
|
||||
|
||||
has(value: T): boolean {
|
||||
return this.#hashmap.has(this.hasher(value));
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.#hashmap.size;
|
||||
}
|
||||
|
||||
entries(): IterableIterator<[T, T]> {
|
||||
let nextIndex = 0;
|
||||
const keys = Array.from(this.keys());
|
||||
const values = Array.from(this.values());
|
||||
|
||||
return makeIterableIterator<[T, T]>({
|
||||
next() {
|
||||
const index = nextIndex++;
|
||||
|
||||
return index < values.length
|
||||
? { value: [keys[index], values[index]], done: false }
|
||||
: { done: true, value: undefined };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
keys(): IterableIterator<T> {
|
||||
return this.values();
|
||||
}
|
||||
|
||||
values(): IterableIterator<T> {
|
||||
let nextIndex = 0;
|
||||
const observableValues = Array.from(this.#hashmap.values());
|
||||
|
||||
return makeIterableIterator<T>({
|
||||
next: () => {
|
||||
return nextIndex < observableValues.length
|
||||
? { value: observableValues[nextIndex++], done: false }
|
||||
: { done: true, value: undefined };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<T> {
|
||||
return this.#hashmap.values();
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag](): string {
|
||||
return "Set";
|
||||
}
|
||||
|
||||
toJSON(): T[] {
|
||||
return Array.from(this);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return "[object ObservableSet]";
|
||||
}
|
||||
}
|
||||
@@ -3,50 +3,5 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./abort-controller";
|
||||
export * from "./autobind";
|
||||
export * from "./camelCase";
|
||||
export * from "./cluster-id-url-parsing";
|
||||
export * from "./collection-functions";
|
||||
export * from "./convertCpu";
|
||||
export * from "./convertMemory";
|
||||
export * from "./debouncePromise";
|
||||
export * from "./delay";
|
||||
export * from "./disposer";
|
||||
export * from "./escapeRegExp";
|
||||
export * from "./formatDuration";
|
||||
export * from "./getRandId";
|
||||
export * from "./hash-set";
|
||||
export * from "./n-fircate";
|
||||
export * from "./noop";
|
||||
export * from "./observable-crate/impl";
|
||||
export * from "./promise-exec";
|
||||
export * from "./readonly";
|
||||
export * from "./reject-promise";
|
||||
export * from "./singleton";
|
||||
export * from "./sort-compare";
|
||||
export * from "./splitArray";
|
||||
export * from "./tar";
|
||||
export * from "./toJS";
|
||||
export * from "./type-narrowing";
|
||||
export * from "./types";
|
||||
export * from "./wait-for-path";
|
||||
export * from "./wait";
|
||||
|
||||
export type { Tuple } from "./tuple";
|
||||
|
||||
import * as iter from "./iter";
|
||||
import * as array from "./array";
|
||||
import * as tuple from "./tuple";
|
||||
import * as base64 from "./base64";
|
||||
import * as object from "./objects";
|
||||
import * as json from "./json";
|
||||
|
||||
export {
|
||||
iter,
|
||||
array,
|
||||
tuple,
|
||||
base64,
|
||||
object,
|
||||
json,
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { isPromise } from "./is-promise";
|
||||
|
||||
describe("isPromise", () => {
|
||||
it("given promise, returns true", () => {
|
||||
const actual = isPromise(new Promise(() => {}));
|
||||
|
||||
expect(actual).toBe(true);
|
||||
});
|
||||
|
||||
it("given non-promise, returns false", () => {
|
||||
const actual = isPromise({});
|
||||
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
|
||||
it("given thenable, returns false", () => {
|
||||
const actual = isPromise({ then: () => {} });
|
||||
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
|
||||
it("given nothing, returns false", () => {
|
||||
const actual = isPromise(undefined);
|
||||
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export function isPromise(reference: any): reference is Promise<any> {
|
||||
return reference?.constructor === Promise;
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export type Falsey = false | 0 | "" | null | undefined;
|
||||
|
||||
interface Iterator<T> extends Iterable<T> {
|
||||
filter(fn: (val: T) => unknown): Iterator<T>;
|
||||
filterMap<U>(fn: (val: T) => Falsey | U): Iterator<U>;
|
||||
find(fn: (val: T) => unknown): T | undefined;
|
||||
collect<U>(fn: (values: Iterable<T>) => U): U;
|
||||
map<U>(fn: (val: T) => U): Iterator<U>;
|
||||
flatMap<U>(fn: (val: T) => U[]): Iterator<U>;
|
||||
concat(src2: IterableIterator<T>): Iterator<T>;
|
||||
join(sep?: string): string;
|
||||
}
|
||||
|
||||
export function chain<T>(src: IterableIterator<T>): Iterator<T> {
|
||||
return {
|
||||
filter: (fn) => chain(filter(src, fn)),
|
||||
filterMap: (fn) => chain(filterMap(src, fn)),
|
||||
map: (fn) => chain(map(src, fn)),
|
||||
flatMap: (fn) => chain(flatMap(src, fn)),
|
||||
find: (fn) => find(src, fn),
|
||||
join: (sep) => join(src, sep),
|
||||
collect: (fn) => fn(src),
|
||||
concat: (src2) => chain(concat(src, src2)),
|
||||
[Symbol.iterator]: () => src,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type safe empty Iterable
|
||||
* @returns An `Iterable` that yields 0 items
|
||||
*/
|
||||
export function* newEmpty<T>(): IterableIterator<T> {}
|
||||
|
||||
/**
|
||||
* Creates a new `Iterable` that yields at most n items from src.
|
||||
* Does not modify `src` which can be used later.
|
||||
* @param src An initial iterator
|
||||
* @param n The maximum number of elements to take from src. Yields up to the floor of `n` and 0 items if n < 0
|
||||
*/
|
||||
export function* take<T>(src: Iterable<T>, n: number): IterableIterator<T> {
|
||||
outer: for (let i = 0; i < n; i += 1) {
|
||||
for (const item of src) {
|
||||
yield item;
|
||||
continue outer;
|
||||
}
|
||||
|
||||
// if we are here that means that `src` has been exhausted. Don't bother trying again.
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new iterator that iterates (lazily) over its input and yields the
|
||||
* result of `fn` for each item.
|
||||
* @param src A type that can be iterated over
|
||||
* @param fn The function that is called for each value
|
||||
*/
|
||||
export function* map<T, U>(src: Iterable<T>, fn: (from: T) => U): IterableIterator<U> {
|
||||
for (const from of src) {
|
||||
yield fn(from);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The single layer flattening of an iterator, discarding `Falsey` values.
|
||||
* @param src A type that can be iterated over
|
||||
* @param fn The function that returns either an iterable over items that should be filtered out or a `Falsey` value indicating that it should be ignored
|
||||
*/
|
||||
export function* filterFlatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U | Falsey> | Falsey): IterableIterator<U> {
|
||||
for (const from of src) {
|
||||
if (!from) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mapping = fn(from);
|
||||
|
||||
if (!mapping) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const mapped of mapping) {
|
||||
if (mapped) {
|
||||
yield mapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new iterator that yields the items that each call to `fn` would produce
|
||||
* @param src A type that can be iterated over
|
||||
* @param fn A function that returns an iterator
|
||||
*/
|
||||
export function* flatMap<T, U>(src: Iterable<T>, fn: (from: T) => Iterable<U>): IterableIterator<U> {
|
||||
for (const from of src) {
|
||||
yield* fn(from);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new iterator that iterates (lazily) over its input and yields the
|
||||
* items that return a `truthy` value from `fn`.
|
||||
* @param src A type that can be iterated over
|
||||
* @param fn The function that is called for each value
|
||||
*/
|
||||
export function* filter<T>(src: Iterable<T>, fn: (from: T) => any): IterableIterator<T> {
|
||||
for (const from of src) {
|
||||
if (fn(from)) {
|
||||
yield from;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new iterator that iterates (lazily) over its input and yields the
|
||||
* result of `fn` when it is `truthy`
|
||||
* @param src A type that can be iterated over
|
||||
* @param fn The function that is called for each value
|
||||
*/
|
||||
export function* filterMap<T, U>(src: Iterable<T>, fn: (from: T) => U | Falsey): IterableIterator<U> {
|
||||
for (const from of src) {
|
||||
const res = fn(from);
|
||||
|
||||
if (res) {
|
||||
yield res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new iterator that iterates (lazily) over its input and yields the
|
||||
* result of `fn` when it is not null or undefined
|
||||
* @param src A type that can be iterated over
|
||||
* @param fn The function that is called for each value
|
||||
*/
|
||||
export function* filterMapStrict<T, U>(src: Iterable<T>, fn: (from: T) => U | null | undefined): IterableIterator<U> {
|
||||
for (const from of src) {
|
||||
const res = fn(from);
|
||||
|
||||
if (res != null) {
|
||||
yield res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate through `src` until `match` returns a truthy value
|
||||
* @param src A type that can be iterated over
|
||||
* @param match A function that should return a truthy value for the item that you want to find
|
||||
* @returns The first entry that `match` returns a truthy value for, or `undefined`
|
||||
*/
|
||||
export function find<T>(src: Iterable<T>, match: (i: T) => any): T | undefined {
|
||||
for (const from of src) {
|
||||
if (match(from)) {
|
||||
return from;
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over `src` calling `reducer` with the previous produced value and the current
|
||||
* yielded value until `src` is exausted. Then return the final value.
|
||||
* @param src The value to iterate over
|
||||
* @param reducer A function for producing the next item from an accumilation and the current item
|
||||
* @param initial The initial value for the iteration
|
||||
*/
|
||||
export function reduce<T, R extends Iterable<any>>(src: Iterable<T>, reducer: (acc: R, cur: T) => R, initial: R): R;
|
||||
|
||||
export function reduce<T, R = T>(src: Iterable<T>, reducer: (acc: R, cur: T) => R, initial: R): R {
|
||||
let acc = initial;
|
||||
|
||||
for (const item of src) {
|
||||
acc = reducer(acc, item);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience function for reducing over an iterator of strings and concatenating them together
|
||||
* @param src The value to iterate over
|
||||
* @param connector The string value to intersperse between the yielded values
|
||||
* @returns The concatenated entries of `src` interspersed with copies of `connector`
|
||||
*/
|
||||
export function join(src: IterableIterator<unknown>, connector = ","): string {
|
||||
const iterSrc = src[Symbol.iterator]();
|
||||
const first = iterSrc.next();
|
||||
|
||||
if (first.done === true) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return reduce(iterSrc, (acc, cur) => `${acc}${connector}${cur}`, `${first.value}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate `n` times and then return the next value.
|
||||
* @param src The value to iterate over
|
||||
* @param n The zero-index value for the item to return to.
|
||||
*/
|
||||
export function nth<T>(src: Iterable<T>, n: number): T | undefined {
|
||||
const iterator = src[Symbol.iterator]();
|
||||
|
||||
while (n --> 0) {
|
||||
iterator.next();
|
||||
}
|
||||
|
||||
return iterator.next().value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience function to get the first item of an iterator
|
||||
* @param src The value to iterate over
|
||||
*/
|
||||
export function first<T>(src: Iterable<T>): T | undefined {
|
||||
return nth(src, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate through `src` and return `true` if `fn` returns a thruthy value for every yielded value.
|
||||
* Otherwise, return `false`. This function short circuits.
|
||||
* @param src The type to be iterated over
|
||||
* @param fn A function to check each iteration
|
||||
*/
|
||||
export function every<T>(src: Iterable<T>, fn: (val: T) => any): boolean {
|
||||
for (const val of src) {
|
||||
if (!fn(val)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function* concat<T>(...sources: IterableIterator<T>[]): IterableIterator<T> {
|
||||
for (const source of sources) {
|
||||
for (const val of source) {
|
||||
yield val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { JsonValue } from "type-fest";
|
||||
|
||||
export function parse(input: string): JsonValue {
|
||||
return JSON.parse(input);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Split an iterable into several arrays with matching fields
|
||||
* @param from The iterable of items to split up
|
||||
* @param field The field of each item to split over
|
||||
* @param parts What each array will be filtered to
|
||||
* @returns A `parts.length` tuple of `T[]` where each array has matching `field` values
|
||||
*/
|
||||
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: []): [];
|
||||
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field]]): [T[]];
|
||||
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field], T[typeof field]]): [T[], T[]];
|
||||
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: [T[typeof field], T[typeof field], T[typeof field]]): [T[], T[], T[]];
|
||||
|
||||
export function nFircate<T>(from: Iterable<T>, field: keyof T, parts: T[typeof field][]): T[][] {
|
||||
if (new Set(parts).size !== parts.length) {
|
||||
throw new TypeError("Duplicate parts entries");
|
||||
}
|
||||
|
||||
const res = Array.from(parts, () => [] as T[]);
|
||||
|
||||
for (const item of from) {
|
||||
const index = parts.indexOf(item[field]);
|
||||
|
||||
if (index < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res[index].push(item);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
/**
|
||||
* A function that does nothing
|
||||
*/
|
||||
export function noop<T extends any[]>(...args: T): void {
|
||||
return void args;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A better typed version of `Object.fromEntries` where the keys are known to
|
||||
* be a specific subset
|
||||
*/
|
||||
export function fromEntries<T, Key extends string>(entries: Iterable<readonly [Key, T]>): Record<Key, T> {
|
||||
return Object.fromEntries(entries) as Record<Key, T>;
|
||||
}
|
||||
|
||||
export function keys<K extends keyof any>(obj: Partial<Record<K, any>>): K[];
|
||||
|
||||
export function keys<K extends keyof any>(obj: Record<K, any>): K[] {
|
||||
return Object.keys(obj) as K[];
|
||||
}
|
||||
|
||||
export function entries<K extends string, V>(obj: Partial<Record<K, V>> | null | undefined): [K, V][];
|
||||
export function entries<K extends string | number | symbol, V>(obj: Partial<Record<K, V>> | null | undefined): [K, V][];
|
||||
export function entries<K extends string | number | symbol, V>(obj: Record<K, V> | null | undefined): [K, V][];
|
||||
|
||||
export function entries<K extends string | number | symbol, V>(obj: Record<K, V> | null | undefined): [K, V][] {
|
||||
if (obj && typeof obj == "object") {
|
||||
return Object.entries(obj) as never;
|
||||
}
|
||||
|
||||
return [] as never;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { observable, runInAction } from "mobx";
|
||||
import { getOrInsertMap } from "../collection-functions";
|
||||
import { noop } from "../noop";
|
||||
|
||||
export interface ObservableCrate<T> {
|
||||
get(): T;
|
||||
set(value: T): void;
|
||||
}
|
||||
|
||||
export interface ObservableCrateFactory {
|
||||
<T>(initialValue: T, transitionHandlers?: ObservableCrateTransitionHandlers<T>): ObservableCrate<T>;
|
||||
}
|
||||
|
||||
export interface ObservableCrateTransitionHandler<T> {
|
||||
from: T;
|
||||
to: T;
|
||||
onTransition: () => void;
|
||||
}
|
||||
export type ObservableCrateTransitionHandlers<T> = ObservableCrateTransitionHandler<T>[];
|
||||
|
||||
function convertToHandlersMap<T>(handlers: ObservableCrateTransitionHandlers<T>): Map<T, Map<T, () => void>> {
|
||||
const res: ReturnType<typeof convertToHandlersMap<T>> = new Map();
|
||||
|
||||
for (const { from, to, onTransition } of handlers) {
|
||||
getOrInsertMap(res, from).set(to, onTransition);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export const observableCrate = ((initialValue, transitionHandlers = []) => {
|
||||
const crate = observable.box(initialValue);
|
||||
const handlers = convertToHandlersMap(transitionHandlers);
|
||||
|
||||
return {
|
||||
get() {
|
||||
return crate.get();
|
||||
},
|
||||
set(value) {
|
||||
const onTransition = handlers.get(crate.get())?.get(value) ?? noop;
|
||||
|
||||
runInAction(() => {
|
||||
crate.set(value);
|
||||
onTransition();
|
||||
});
|
||||
},
|
||||
};
|
||||
}) as ObservableCrateFactory;
|
||||
@@ -1,116 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ObservableCrate } from "./impl";
|
||||
import { observableCrate } from "./impl";
|
||||
|
||||
describe("observable-crate", () => {
|
||||
it("can be constructed with initial value", () => {
|
||||
expect(() => observableCrate(0)).not.toThrow();
|
||||
});
|
||||
|
||||
it("has a definite type if the initial value is provided", () => {
|
||||
expect (() => {
|
||||
const res: ObservableCrate<number> = observableCrate(0);
|
||||
|
||||
void res;
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("accepts an array of transitionHandlers", () => {
|
||||
expect(() => observableCrate(0, [])).not.toThrow();
|
||||
});
|
||||
|
||||
describe("with a crate over an enum, and some transition handlers", () => {
|
||||
enum Test {
|
||||
Start,
|
||||
T1,
|
||||
End,
|
||||
}
|
||||
|
||||
let crate: ObservableCrate<Test>;
|
||||
let correctHandler: jest.MockedFunction<() => void>;
|
||||
let incorrectHandler: jest.MockedFunction<() => void>;
|
||||
|
||||
beforeEach(() => {
|
||||
correctHandler = jest.fn();
|
||||
incorrectHandler = jest.fn();
|
||||
crate = observableCrate(Test.Start, [
|
||||
{
|
||||
from: Test.Start,
|
||||
to: Test.Start,
|
||||
onTransition: incorrectHandler,
|
||||
},
|
||||
{
|
||||
from: Test.Start,
|
||||
to: Test.T1,
|
||||
onTransition: correctHandler,
|
||||
},
|
||||
{
|
||||
from: Test.Start,
|
||||
to: Test.End,
|
||||
onTransition: incorrectHandler,
|
||||
},
|
||||
{
|
||||
from: Test.T1,
|
||||
to: Test.Start,
|
||||
onTransition: incorrectHandler,
|
||||
},
|
||||
{
|
||||
from: Test.T1,
|
||||
to: Test.T1,
|
||||
onTransition: incorrectHandler,
|
||||
},
|
||||
{
|
||||
from: Test.T1,
|
||||
to: Test.End,
|
||||
onTransition: incorrectHandler,
|
||||
},
|
||||
{
|
||||
from: Test.End,
|
||||
to: Test.Start,
|
||||
onTransition: incorrectHandler,
|
||||
},
|
||||
{
|
||||
from: Test.End,
|
||||
to: Test.T1,
|
||||
onTransition: incorrectHandler,
|
||||
},
|
||||
{
|
||||
from: Test.End,
|
||||
to: Test.End,
|
||||
onTransition: incorrectHandler,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("initial value is available", () => {
|
||||
expect(crate.get()).toBe(Test.Start);
|
||||
});
|
||||
|
||||
it("does not call any transition handler", () => {
|
||||
expect(correctHandler).not.toBeCalled();
|
||||
expect(incorrectHandler).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe("when setting a new value", () => {
|
||||
beforeEach(() => {
|
||||
crate.set(Test.T1);
|
||||
});
|
||||
|
||||
it("calls the associated transition handler", () => {
|
||||
expect(correctHandler).toBeCalled();
|
||||
});
|
||||
|
||||
it("does not call any other transition handler", () => {
|
||||
expect(incorrectHandler).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("new value is available", () => {
|
||||
expect(crate.get()).toBe(Test.T1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
||||
import openLinkInBrowserInjectable from "./open-link-in-browser.injectable";
|
||||
|
||||
export default getGlobalOverride(openLinkInBrowserInjectable, () => async () => {});
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import * as util from "util";
|
||||
import { execFile } from "child_process";
|
||||
|
||||
export const promiseExecFile = util.promisify(execFile);
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
||||
import randomBytesInjectable from "./random-bytes.injectable";
|
||||
|
||||
export default getGlobalOverride(randomBytesInjectable, () => (size) => {
|
||||
|
||||
@@ -8,8 +8,8 @@ import type { IComputedValue } from "mobx";
|
||||
import { computed, observe } from "mobx";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { advanceFakeTime, testUsingFakeTime } from "../../test-utils/use-fake-time";
|
||||
import { reactiveNow } from "./reactive-now";
|
||||
import { advanceFakeTime, testUsingFakeTime } from "../../../test-utils/use-fake-time";
|
||||
|
||||
describe("reactiveNow", () => {
|
||||
let someComputed: IComputedValue<boolean>;
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { Readable } from "readable-stream";
|
||||
import type { TypedArray } from "type-fest";
|
||||
|
||||
/**
|
||||
* ReadableWebToNodeStream
|
||||
*
|
||||
* Copied from https://github.com/Borewit/readable-web-to-node-stream
|
||||
*
|
||||
* Adds read error handler
|
||||
*
|
||||
* */
|
||||
export class ReadableWebToNodeStream<T extends TypedArray> extends Readable {
|
||||
|
||||
public bytesRead = 0;
|
||||
public released = false;
|
||||
|
||||
/**
|
||||
* Default web API stream reader
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader
|
||||
*/
|
||||
private reader: ReadableStreamDefaultReader<T>;
|
||||
private pendingRead?: Promise<ReadableStreamReadResult<T>>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param stream ReadableStream: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
|
||||
*/
|
||||
constructor(stream: ReadableStream<T>) {
|
||||
super();
|
||||
this.reader = stream.getReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of readable._read(size).
|
||||
* When readable._read() is called, if data is available from the resource,
|
||||
* the implementation should begin pushing that data into the read queue
|
||||
* https://nodejs.org/api/stream.html#stream_readable_read_size_1
|
||||
*/
|
||||
public async _read() {
|
||||
// Should start pushing data into the queue
|
||||
// Read data from the underlying Web-API-readable-stream
|
||||
if (this.released) {
|
||||
this.push(null); // Signal EOF
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.pendingRead = this.reader.read();
|
||||
const data = await this.pendingRead;
|
||||
|
||||
// clear the promise before pushing pushing new data to the queue and allow sequential calls to _read()
|
||||
delete this.pendingRead;
|
||||
|
||||
if (data.done || this.released) {
|
||||
this.push(null); // Signal EOF
|
||||
} else {
|
||||
this.bytesRead += data.value.length;
|
||||
this.push(data.value); // Push new data to the queue
|
||||
}
|
||||
} catch (error) {
|
||||
this.push(null); // Signal EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is no unresolved read call to Web-API ReadableStream immediately returns;
|
||||
* otherwise will wait until the read is resolved.
|
||||
*/
|
||||
public async waitForReadToComplete() {
|
||||
if (this.pendingRead) {
|
||||
await this.pendingRead;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close wrapper
|
||||
*/
|
||||
public async close(): Promise<void> {
|
||||
await this.syncAndRelease();
|
||||
}
|
||||
|
||||
private async syncAndRelease() {
|
||||
this.released = true;
|
||||
await this.waitForReadToComplete();
|
||||
await this.reader.releaseLock();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ReadonlyDeep } from "type-fest";
|
||||
|
||||
export function readonly<T>(src: T): ReadonlyDeep<T> {
|
||||
return src as ReadonlyDeep<T>;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { AbortSignal } from "abort-controller";
|
||||
|
||||
/**
|
||||
* Creates a new promise that will be rejected when the signal rejects.
|
||||
*
|
||||
* Useful for `Promise.race()` applications.
|
||||
* @param signal The AbortController's signal to reject with
|
||||
*/
|
||||
export function rejectPromiseBy(signal: AbortSignal): Promise<never> {
|
||||
return new Promise((_, reject) => {
|
||||
signal.addEventListener("abort", reject);
|
||||
});
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import semver, { coerce } from "semver";
|
||||
|
||||
export enum Ordering {
|
||||
LESS = -1,
|
||||
EQUAL = 0,
|
||||
GREATER = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* This function switches the direction of `ordering` if `direction` is `"desc"`
|
||||
* @param ordering The original ordering (assumed to be an "asc" ordering)
|
||||
* @param direction The new desired direction
|
||||
*/
|
||||
export function rectifyOrdering(ordering: Ordering, direction: "asc" | "desc"): Ordering {
|
||||
if (direction === "desc") {
|
||||
return -ordering;
|
||||
}
|
||||
|
||||
return ordering;
|
||||
}
|
||||
|
||||
/**
|
||||
* An ascending sorting function
|
||||
* @param left An item from an array
|
||||
* @param right An item from an array
|
||||
* @returns The relative ordering in an ascending manner.
|
||||
* - Less if left < right
|
||||
* - Equal if left == right
|
||||
* - Greater if left > right
|
||||
*/
|
||||
export function sortCompare<T>(left: T, right: T): Ordering {
|
||||
if (left < right) {
|
||||
return Ordering.LESS;
|
||||
}
|
||||
|
||||
if (left === right) {
|
||||
return Ordering.EQUAL;
|
||||
}
|
||||
|
||||
return Ordering.GREATER;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function sorts of list of items that have what should be a semver version formated string
|
||||
* as the field `version` but if it is not loosely coercable to semver falls back to sorting them
|
||||
* alphanumerically
|
||||
*/
|
||||
export function sortBySemverVersion<T extends { version: string }>(versioneds: T[]): T[] {
|
||||
return versioneds
|
||||
.map(versioned => ({
|
||||
__version: coerce(versioned.version, { loose: true }),
|
||||
raw: versioned,
|
||||
}))
|
||||
.sort((left, right) => {
|
||||
if (left.__version && right.__version) {
|
||||
return semver.compare(right.__version, left.__version);
|
||||
}
|
||||
|
||||
if (!left.__version && right.__version) {
|
||||
return Ordering.GREATER;
|
||||
}
|
||||
|
||||
if (left.__version && !right.__version) {
|
||||
return Ordering.LESS;
|
||||
}
|
||||
|
||||
return sortCompare(left.raw.version, right.raw.version);
|
||||
})
|
||||
.map(({ raw }) => raw);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get an ordering function based on the function getter
|
||||
*/
|
||||
export function byValue<T>(getOrderValue: (src: T) => number): (left: T, right: T) => number {
|
||||
return (left, right) => {
|
||||
const leftValue = getOrderValue(left);
|
||||
const rightValue = getOrderValue(right);
|
||||
|
||||
return leftValue - rightValue;
|
||||
};
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function splits an array into two sub arrays on the first instance of
|
||||
* element (from the left). If the array does not contain the element. The
|
||||
* return value is defined to be `[array, [], false]`. If the element is in
|
||||
* the array then the return value is `[left, right, true]` where `left` is
|
||||
* the elements of `array` from `[0, index)` and `right` is `(index, length)`
|
||||
* @param array the full array to split into two sub-arrays
|
||||
* @param element the element in the middle of the array
|
||||
* @returns the left and right sub-arrays which when conjoined with `element`
|
||||
* is the same as `array`, and `true`
|
||||
*/
|
||||
export function splitArray<T>(array: T[], element: T): [T[], T[], boolean] {
|
||||
const index = array.indexOf(element);
|
||||
|
||||
if (index < 0) {
|
||||
return [array, [], false];
|
||||
}
|
||||
|
||||
return [array.slice(0, index), array.slice(index + 1, array.length), true];
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits an array into two parts based on the outcome of `condition`. If `true`
|
||||
* the value will be returned as part of the right array. If `false` then part of
|
||||
* the left array.
|
||||
* @param src the full array to bifurcate
|
||||
* @param condition the function to determine which set each is in
|
||||
*/
|
||||
export function bifurcateArray<T>(src: T[], condition: (item: T) => boolean): [falses: T[], trues: T[]] {
|
||||
const res: [T[], T[]] = [[], []];
|
||||
|
||||
for (const item of src) {
|
||||
res[+condition(item)].push(item);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Helper for working with tarball files (.tar, .tgz)
|
||||
// Docs: https://github.com/npm/node-tar
|
||||
import tar from "tar";
|
||||
import path from "path";
|
||||
import { parse } from "./json";
|
||||
import type { JsonValue } from "type-fest";
|
||||
|
||||
export type ReadFileFromTarOpts<ParseJson extends boolean> = {
|
||||
tarPath: string;
|
||||
filePath: string;
|
||||
} & (
|
||||
ParseJson extends true
|
||||
? {
|
||||
parseJson: true;
|
||||
}
|
||||
: {
|
||||
parseJson?: false;
|
||||
}
|
||||
);
|
||||
|
||||
export function readFileFromTar(opts: ReadFileFromTarOpts<false>): Promise<Buffer>;
|
||||
export function readFileFromTar(opts: ReadFileFromTarOpts<true>): Promise<JsonValue>;
|
||||
|
||||
export function readFileFromTar<ParseJson extends boolean>({ tarPath, filePath, parseJson = false }: ReadFileFromTarOpts<ParseJson>): Promise<JsonValue | Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileChunks: Buffer[] = [];
|
||||
|
||||
tar.list({
|
||||
file: tarPath,
|
||||
filter: entryPath => path.normalize(entryPath) === filePath,
|
||||
sync: true,
|
||||
onentry(entry) {
|
||||
entry.on("data", chunk => {
|
||||
fileChunks.push(chunk);
|
||||
});
|
||||
entry.once("error", err => {
|
||||
reject(new Error(`reading file has failed ${entry.path}: ${err}`));
|
||||
});
|
||||
entry.once("end", () => {
|
||||
const data = Buffer.concat(fileChunks);
|
||||
const result = parseJson ? parse(data.toString("utf8")) : data;
|
||||
|
||||
resolve(result);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!fileChunks.length) {
|
||||
reject(new Error("Not found"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function listTarEntries(filePath: string): Promise<string[]> {
|
||||
const entries: string[] = [];
|
||||
|
||||
await tar.list({
|
||||
file: filePath,
|
||||
onentry: (entry) => {
|
||||
entries.push(path.normalize(entry.path));
|
||||
},
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
@@ -13,6 +13,9 @@
|
||||
import * as mobx from "mobx";
|
||||
import { isObservable, observable } from "mobx";
|
||||
|
||||
/**
|
||||
* @deprecated Switch to doing toJS on each field instead
|
||||
*/
|
||||
export function toJS<T>(data: T): T {
|
||||
// make data observable for recursive toJS()-output
|
||||
if (typeof data === "object" && !isObservable(data)) {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import * as array from "../utils/array";
|
||||
|
||||
/**
|
||||
* A strict N-tuple of type T
|
||||
*/
|
||||
export type Tuple<T, N extends number> = N extends N
|
||||
? number extends N
|
||||
? T[]
|
||||
: TupleOfImpl<T, N, []>
|
||||
: never;
|
||||
type TupleOfImpl<T, N extends number, R extends unknown[]> = R["length"] extends N
|
||||
? R
|
||||
: TupleOfImpl<T, N, [T, ...R]>;
|
||||
|
||||
/**
|
||||
* Iterates over `sources` yielding full tuples until one of the tuple arrays
|
||||
* is empty. Then it returns a tuple with the rest of each of tuples
|
||||
* @param sources The source arrays
|
||||
* @yields A tuple of the next element from each of the sources
|
||||
* @returns The tuple of all the sources as soon as at least one of the sources is exausted
|
||||
*/
|
||||
export function zip<T>(...sources: Tuple<T[], 0>): Iterator<Tuple<T, 0>, Tuple<T[], 0>>;
|
||||
export function zip<T>(...sources: Tuple<T[], 1>): Iterator<Tuple<T, 1>, Tuple<T[], 1>>;
|
||||
export function zip<T>(...sources: Tuple<T[], 2>): Iterator<Tuple<T, 2>, Tuple<T[], 2>>;
|
||||
|
||||
export function* zip<T, N extends number>(...sources: Tuple<T[], N>): Iterator<Tuple<T, N>, Tuple<T[], N>> {
|
||||
const maxSafeLength = Math.min(...sources.map(source => source.length));
|
||||
|
||||
for (let i = 0; i < maxSafeLength; i += 1) {
|
||||
yield sources.map(source => source[i]) as Tuple<T, N>;
|
||||
}
|
||||
|
||||
return sources.map(source => source.slice(maxSafeLength)) as Tuple<T[], N>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `length` tuple filled with copies of `value`
|
||||
* @param length The size of the tuple
|
||||
* @param value The value for each of the tuple entries
|
||||
*/
|
||||
export function filled<T, L extends number>(length: L, value: T): Tuple<T, L> {
|
||||
return array.filled(length, value) as Tuple<T, L>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function for converting an explicit array to a tuple but without the `readonly` typing
|
||||
*/
|
||||
export function from<T extends any[]>(...args: T): [...T] {
|
||||
return args;
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ExecException, ExecFileException } from "child_process";
|
||||
import type { IncomingMessage } from "http";
|
||||
|
||||
/**
|
||||
* Narrows `val` to include the property `key` (if true is returned)
|
||||
* @param val The object to be tested
|
||||
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||
*/
|
||||
export function hasOwnProperty<S extends object, K extends PropertyKey>(val: S, key: K): val is (S & { [key in K]: unknown }) {
|
||||
// this call syntax is for when `val` was created by `Object.create(null)`
|
||||
return Object.prototype.hasOwnProperty.call(val, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows `val` to a static type that includes fields of names in `keys`
|
||||
* @param val the value that we are trying to type narrow
|
||||
* @param keys the key names (must be literals for tsc to do any meaningful typing)
|
||||
*/
|
||||
export function hasOwnProperties<S extends object, K extends PropertyKey>(val: S, ...keys: K[]): val is (S & { [key in K]: unknown }) {
|
||||
return keys.every(key => hasOwnProperty(val, key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows `val` to include the property `key` with type `V`
|
||||
* @param val the value that we are trying to type narrow
|
||||
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||
* @param isValid a function to check if the field is valid
|
||||
*/
|
||||
export function hasTypedProperty<S extends object, K extends PropertyKey, V>(val: S, key: K, isValid: (value: unknown) => value is V): val is (S & { [key in K]: V }) {
|
||||
return hasOwnProperty(val, key) && isValid(val[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows `val` to include the property `key` with type string
|
||||
* @param val the value that we are trying to type narrow
|
||||
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||
*/
|
||||
export function hasStringProperty<S extends object, K extends PropertyKey>(val: S, key: K): val is (S & { [key in K]: string }) {
|
||||
return hasOwnProperty(val, key) && isString(val[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows `val` to include the property `key` with type `V | undefined` or doesn't contain it
|
||||
* @param val the value that we are trying to type narrow
|
||||
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||
* @param isValid a function to check if the field (when present) is valid
|
||||
*/
|
||||
export function hasOptionalTypedProperty<S extends object, K extends PropertyKey, V>(val: S, key: K, isValid: (value: unknown) => value is V): val is (S & { [key in K]?: V }) {
|
||||
if (hasOwnProperty(val, key)) {
|
||||
return typeof val[key] === "undefined" || isValid(val[key]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* isRecord checks if `val` matches the signature `Record<T, V>` or `{ [label in T]: V }`
|
||||
* @param val The value to be checked
|
||||
* @param isKey a function for checking if the key is of the correct type
|
||||
* @param isValue a function for checking if a value is of the correct type
|
||||
*/
|
||||
export function isRecord<T extends PropertyKey, V>(val: unknown, isKey: (key: unknown) => key is T, isValue: (value: unknown) => value is V): val is Record<T, V> {
|
||||
return isObject(val) && Object.entries(val).every(([key, value]) => isKey(key) && isValue(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* isTypedArray checks if `val` is an array and all of its entries are of type `T`
|
||||
* @param val The value to be checked
|
||||
* @param isEntry a function for checking if an entry is the correct type
|
||||
*/
|
||||
export function isTypedArray<T>(val: unknown, isEntry: (entry: unknown) => entry is T): val is T[] {
|
||||
return Array.isArray(val) && val.every(isEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if val is of type string
|
||||
* @param val the value to be checked
|
||||
*/
|
||||
export function isString(val: unknown): val is string {
|
||||
return typeof val === "string";
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if val is of type Buffer
|
||||
* @param val the value to be checked
|
||||
*/export function isBuffer(val: unknown): val is Buffer {
|
||||
return val instanceof Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if val is of type number
|
||||
* @param val the value to be checked
|
||||
*/
|
||||
export function isNumber(val: unknown): val is number {
|
||||
return typeof val === "number";
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if val is of type boolean
|
||||
* @param val the value to be checked
|
||||
*/
|
||||
export function isBoolean(val: unknown): val is boolean {
|
||||
return typeof val === "boolean";
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if val is of type object and isn't null
|
||||
* @param val the value to be checked
|
||||
*/
|
||||
export function isObject(val: unknown): val is Record<string | symbol | number, unknown> {
|
||||
return typeof val === "object" && val !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if `val` is defined, useful for filtering out undefined values in a strict manner
|
||||
*/
|
||||
export function isDefined<T>(val: T | undefined | null): val is T {
|
||||
return val != null;
|
||||
}
|
||||
|
||||
export function isFunction(val: unknown): val is (...args: unknown[]) => unknown {
|
||||
return typeof val === "function";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value in the second position is non-nullable
|
||||
*/
|
||||
export function hasDefinedTupleValue<K, V>(pair: readonly [K, V | undefined | null]): pair is [K, V] {
|
||||
return pair[1] != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new predicate function (with the same predicate) from `fn`. Such
|
||||
* that it can be called with just the value to be tested.
|
||||
*
|
||||
* This is useful for when using `hasOptionalProperty` and `hasTypedProperty`
|
||||
* @param fn A typescript user predicate function to be bound
|
||||
* @param boundArgs the set of arguments to be passed to `fn` in the new function
|
||||
*/
|
||||
export function bindPredicate<FnArgs extends any[], T>(fn: (arg1: unknown, ...args: FnArgs) => arg1 is T, ...boundArgs: FnArgs): (arg1: unknown) => arg1 is T {
|
||||
return (arg1: unknown): arg1 is T => fn(arg1, ...boundArgs);
|
||||
}
|
||||
|
||||
export function hasDefiniteField<Field extends keyof T, T>(field: Field): (val: T) => val is T & { [f in Field]-?: NonNullable<T[Field]> } {
|
||||
return (val): val is T & { [f in Field]-?: NonNullable<T[Field]> } => val[field] != null;
|
||||
}
|
||||
|
||||
export function isPromiseLike(res: unknown): res is (Promise<unknown> | { then: (fn: (val: unknown) => any) => Promise<unknown> }) {
|
||||
if (res instanceof Promise) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isObject(res)
|
||||
&& hasTypedProperty(res, "then", isFunction);
|
||||
}
|
||||
|
||||
export function isPromiseSettledRejected<T>(result: PromiseSettledResult<T>): result is PromiseRejectedResult {
|
||||
return result.status === "rejected";
|
||||
}
|
||||
|
||||
export function isPromiseSettledFulfilled<T>(result: PromiseSettledResult<T>): result is PromiseFulfilledResult<T> {
|
||||
return result.status === "fulfilled";
|
||||
}
|
||||
|
||||
export function isErrnoException(error: unknown): error is NodeJS.ErrnoException {
|
||||
return isObject(error)
|
||||
&& hasOptionalTypedProperty(error, "code", isString)
|
||||
&& hasOptionalTypedProperty(error, "path", isString)
|
||||
&& hasOptionalTypedProperty(error, "syscall", isString)
|
||||
&& hasOptionalTypedProperty(error, "errno", isNumber)
|
||||
&& error instanceof Error;
|
||||
}
|
||||
|
||||
export function isExecException(error: unknown): error is ExecException {
|
||||
return isObject(error)
|
||||
&& hasOptionalTypedProperty(error, "cmd", isString)
|
||||
&& hasOptionalTypedProperty(error, "killed", isBoolean)
|
||||
&& hasOptionalTypedProperty(error, "signal", isString)
|
||||
&& hasOptionalTypedProperty(error, "code", isNumber)
|
||||
&& error instanceof Error;
|
||||
}
|
||||
|
||||
export function isExecFileException(error: unknown): error is ExecFileException {
|
||||
return isExecException(error) && isErrnoException(error);
|
||||
}
|
||||
|
||||
export type OutputFormat = "string" | "buffer";
|
||||
export type ComputeOutputFormat<Format> = Format extends "string"
|
||||
? string
|
||||
: Format extends "buffer"
|
||||
? Buffer
|
||||
: string | Buffer;
|
||||
|
||||
export interface ChildProcessExecpetion<Format> extends ExecFileException {
|
||||
stderr: ComputeOutputFormat<Format>;
|
||||
stdout: ComputeOutputFormat<Format>;
|
||||
}
|
||||
|
||||
const isStringOrBuffer = (val: unknown): val is string | Buffer => isString(val) || isBuffer(val);
|
||||
|
||||
export function isChildProcessError(error: unknown, format?: OutputFormat): error is ChildProcessExecpetion<typeof format> {
|
||||
if (!isExecFileException(error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (format === "string") {
|
||||
return hasTypedProperty(error, "stderr", isString)
|
||||
&& hasTypedProperty(error, "stdout", isString);
|
||||
} else if (format === "buffer") {
|
||||
return hasTypedProperty(error, "stderr", isBuffer)
|
||||
&& hasTypedProperty(error, "stdout", isBuffer);
|
||||
} else {
|
||||
return hasTypedProperty(error, "stderr", isStringOrBuffer)
|
||||
&& hasTypedProperty(error, "stdout", isStringOrBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
export interface RequestLikeError extends Error {
|
||||
statusCode?: number;
|
||||
failed?: boolean;
|
||||
timedOut?: boolean;
|
||||
error?: string;
|
||||
response?: IncomingMessage & { body?: any };
|
||||
}
|
||||
|
||||
/**
|
||||
* A type guard for checking if the error is similar in shape to a request package error
|
||||
*/
|
||||
export function isRequestError(error: unknown): error is RequestLikeError {
|
||||
return isObject(error)
|
||||
&& hasOptionalTypedProperty(error, "statusCode", isNumber)
|
||||
&& hasOptionalTypedProperty(error, "failed", isBoolean)
|
||||
&& hasOptionalTypedProperty(error, "timedOut", isBoolean)
|
||||
&& hasOptionalTypedProperty(error, "error", isString)
|
||||
&& hasOptionalTypedProperty(error, "response", isObject)
|
||||
&& error instanceof Error;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { SetRequired } from "type-fest";
|
||||
|
||||
export type RemoveUndefinedFromValues<K> = {
|
||||
[P in keyof K]: NonNullable<K[P]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* This type helps define which fields of some type will always be defined
|
||||
*/
|
||||
export type Defaulted<Params, DefaultParams extends keyof Params> = RemoveUndefinedFromValues<Required<Pick<Params, DefaultParams>>> & Omit<Params, DefaultParams>;
|
||||
|
||||
export type OptionVarient<Key, Base, RequiredKey extends keyof Base> = {
|
||||
type: Key;
|
||||
} & Pick<Base, RequiredKey> & {
|
||||
[OtherKey in Exclude<keyof Base, RequiredKey>]?: undefined;
|
||||
};
|
||||
|
||||
export type SingleOrMany<T> = T | T[];
|
||||
|
||||
export type IfEquals<T, U, Y=unknown, N=never> =
|
||||
(<G>() => G extends T ? 1 : 2) extends
|
||||
(<G>() => G extends U ? 1 : 2) ? Y : N;
|
||||
|
||||
export type MaybeSetRequired<BaseType, Keys extends keyof BaseType, Query> = Query extends true
|
||||
? SetRequired<BaseType, Keys>
|
||||
: BaseType;
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import * as iter from "./iter";
|
||||
|
||||
/**
|
||||
* Join all entires with a PATH env var delimated string together
|
||||
* @param PATHs Any number of PATH env variables
|
||||
*
|
||||
* NOTE: This function does not attempt to handle any sort of escape sequences since after testing
|
||||
* it was found that `zsh` (at least on `macOS`) does not when trying to find programs
|
||||
*/
|
||||
export function unionPATHs(...PATHs: string[]): string {
|
||||
const entries = new Set(iter.filterFlatMap(PATHs, PATH => PATH.split(path.delimiter)));
|
||||
|
||||
return iter.join(entries.values(), path.delimiter);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { runInAction, when } from "mobx";
|
||||
import type { Disposer } from "./disposer";
|
||||
|
||||
export async function waitUntilDefined<T>(getter: (() => T | null | undefined) | IComputedValue<T | null | undefined>, opts?: { timeout?: number }): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
when(
|
||||
() => {
|
||||
const res = typeof getter === "function"
|
||||
? getter()
|
||||
: getter.get();
|
||||
const isDefined = res != null;
|
||||
|
||||
if (isDefined) {
|
||||
resolve(res);
|
||||
}
|
||||
|
||||
return isDefined;
|
||||
},
|
||||
() => {},
|
||||
{
|
||||
onError: reject,
|
||||
...(opts ?? {}),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function onceDefined<T>(getter: () => T | null | undefined, action: (val: T) => void): Disposer {
|
||||
let res: T | null | undefined;
|
||||
|
||||
return when(
|
||||
() => {
|
||||
res = getter();
|
||||
|
||||
if (res != null) {
|
||||
const r = res;
|
||||
|
||||
runInAction(() => {
|
||||
action(r);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import plimit from "p-limit";
|
||||
|
||||
export type ConcurrencyLimiter = <Args extends any[], Res>(fn: (...args: Args) => Res) => (...args: Args) => Promise<Res>;
|
||||
|
||||
export function withConcurrencyLimit(limit: number): ConcurrencyLimiter {
|
||||
const limiter = plimit(limit);
|
||||
|
||||
return fn => (...args) => limiter(() => fn(...args));
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import logErrorInjectable from "../../log-error.injectable";
|
||||
import { isPromise } from "../is-promise/is-promise";
|
||||
|
||||
export type WithErrorLoggingFor = (
|
||||
getErrorMessage: (error: unknown) => string
|
||||
@@ -33,7 +32,7 @@ const withErrorLoggingInjectable = getInjectable({
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (isPromise(returnValue)) {
|
||||
if ((returnValue as any) instanceof Promise) {
|
||||
return returnValue.catch((e: unknown) => {
|
||||
const errorMessage = getErrorMessage(e);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import withErrorLoggingInjectable from "./with-error-logging.injectable";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import { getPromiseStatus } from "../../test-utils/get-promise-status";
|
||||
import { getPromiseStatus } from "@k8slens/test-utils";
|
||||
import logErrorInjectable from "../../log-error.injectable";
|
||||
|
||||
describe("with-error-logging", () => {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import { getPromiseStatus } from "../../test-utils/get-promise-status";
|
||||
import { getPromiseStatus } from "@k8slens/test-utils";
|
||||
import { withErrorSuppression } from "./with-error-suppression";
|
||||
|
||||
describe("with-error-suppression", () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ export function withErrorSuppression(toBeDecorated: any) {
|
||||
try {
|
||||
const returnValue = toBeDecorated(...args);
|
||||
|
||||
if (isPromise(returnValue)) {
|
||||
if ((returnValue as any) instanceof Promise) {
|
||||
return returnValue.catch(noop);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,3 @@ export function withErrorSuppression(toBeDecorated: any) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function isPromise(reference: any): reference is Promise<any> {
|
||||
return !!reference?.then;
|
||||
}
|
||||
Reference in new issue
Block a user