Release package version 1.0.0
All checks were successful
CI / build (push) Successful in 33s

Added first files
This commit is contained in:
Andrey Kernichniy 2026-03-13 00:32:06 +07:00
parent 3be22f1023
commit aad2fe13e7
29 changed files with 5020 additions and 8 deletions

View file

@ -0,0 +1,133 @@
import { difference, intersection, sliceByCount, sortByKey, uniq } from "./array";
import { stringEquals } from "./string";
describe("Array intersection", () => {
it("Equal length", () => {
const result = intersection([
[1, 2, 3],
[1, 2, 4],
]);
expect(result).toEqual([1, 2]);
});
it("Not equal length", () => {
const result = intersection([[1, 2, 3], [1, 2, 4, 6], [1]]);
expect(result).toEqual([1]);
});
it("Strings", () => {
const result = intersection([["apple", "box", "easy"], ["apple", "box", "simple"], ["apple"]]);
expect(result).toEqual(["apple"]);
});
it("Using equal function", () => {
const result = intersection(
[["Apple", "box", "easy"], ["apple", "box", "simple"], ["Apple"]],
(a, b) => a.toLocaleLowerCase() === b.toLocaleLowerCase(),
);
expect(result).toEqual(["Apple"]);
});
it("Call with empty array", () => {
const result = intersection([]);
expect(result).toEqual([]);
});
});
describe("uniq", () => {
it("", () => {
const result = uniq([]);
expect(result).toEqual([]);
});
it("", () => {
const result = uniq([1, 2, 3, 4]);
expect(result).toEqual([1, 2, 3, 4]);
});
it("Check numbers", () => {
const result = uniq([1, 2, 3, 4, 4, 1, 2]);
expect(result).toEqual([1, 2, 3, 4]);
});
it("Check numbers", () => {
const result = uniq(["1", "2", "3", "4", "4", "1", "2"]);
expect(result).toEqual(["1", "2", "3", "4"]);
});
it("Check float numbers", () => {
const result = uniq([3.14, 3.15, 3.14, 3]);
expect(result).toEqual([3.14, 3.15, 3]);
});
it("Custom equal function", () => {
const result = uniq([3.14, 3.15, 3.14, 3], (item1, item2) => Math.abs(item1 - item2) < 0.1);
expect(result).toEqual([3.14, 3]);
});
it("Base equality objects", () => {
const a = {};
const b = { b: "b" };
const result = uniq([a, b, b, a]);
expect(result).toEqual([a, b]);
});
});
describe("sortByKey function", () => {
it("Empty array", () => {
const result = sortByKey([] as Array<{ a: string }>, "a");
expect(result).toEqual([]);
});
it("Number values", () => {
const result = sortByKey([{ a: 1 }, { a: 4 }, { a: 6 }, { a: 3 }, { a: 0 }], "a");
expect(result).toEqual([{ a: 0 }, { a: 1 }, { a: 3 }, { a: 4 }, { a: 6 }]);
});
it("String values", () => {
const result = sortByKey([{ a: "a" }, { a: "z" }, { a: "g" }, { a: "s" }, { a: "b" }], "a");
expect(result).toEqual([{ a: "a" }, { a: "b" }, { a: "g" }, { a: "s" }, { a: "z" }]);
});
it("String with complex values", () => {
const result = sortByKey([{ a: "Banana" }, { a: "Orange" }, { a: "apple" }, { a: "Kiwi" }], "a");
expect(result).toEqual([{ a: "Banana" }, { a: "Kiwi" }, { a: "Orange" }, { a: "apple" }]);
});
});
describe("difference function", () => {
it("Base work", () => {
const result = difference([1, 2, 3, 4, 5, 6, 7, 8, 9], undefined, [2, 4], [6, 8]);
expect(result).toEqual([1, 3, 5, 7, 9]);
});
it("Empty array", () => {
const result = difference([], undefined, [2, 4], [6, 8]);
expect(result).toEqual([]);
});
it("Call with only base array", () => {
const result = difference([1, 2, 3, 4, 5, 6, 7, 8, 9]);
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
it("Custom equal function", () => {
const result = difference(
["apple", "banana", "orange", "kiwi", "blackberry"],
(item1, item2) => stringEquals(item1, item2, true),
["Apple", "oraNge"],
["KIWI"],
);
expect(result).toEqual(["banana", "blackberry"]);
});
});
describe("sliceByCount function", () => {
it("Base work", () => {
const array = sliceByCount([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 7);
expect(array).toEqual([
[1, 2, 3, 4, 5, 6, 7],
[8, 9, 10],
]);
});
});

102
lib/src/algorithms/array.ts Normal file
View file

@ -0,0 +1,102 @@
/**
* Function check that all elements of array is equals. If type of array is complex, pass function for compotation function.
*/
export const allValuesEqual = <T>(arr: T[], equalFunc: (current: T, next: T) => boolean = (current, next) => current === next): boolean => {
if (arr.length === 1) return true;
for (let i = 0, j = 1; i < arr.length; i++, j++) {
const current = arr[i];
const next = arr[j];
const nonEqual = !equalFunc(current, next);
if (j === arr.length) return true;
if (nonEqual) return false;
}
};
/** This is value used as Mixed value */
export const MIXED_VALUE = undefined;
/** Return element of array if all elements is equals, else return Mixed (`undefined`) value */
export const valueOrMixed = <T = any>(collector: () => Array<T>, equalFunc?: (current: T, next: T) => boolean): T => {
const values = collector();
return allValuesEqual(values, equalFunc) ? values[0] : MIXED_VALUE;
};
/** Function checks array equality */
export const arraysIsEqual = <T>(arr1: T[], arr2: T[], equalFunc: (item1: T, item2: T) => boolean = (item1, item2) => item1 === item2) => {
if (arr1 === arr2) return true;
if (arr1.length != arr2.length) return false;
let result = true;
for (let i = 0; i < arr1.length; i++) {
result = result && equalFunc(arr1[i], arr2[i]);
}
return result;
};
export const intersection = <T>(
arrays: Array<T>[],
equalFunc: (item1: T, item2: T) => boolean = (item1, item2) => item1 === item2,
): Array<T> => {
if (arrays.length === 0) return [];
const sortedArray = arrays.sort((a, b) => a.length - b.length);
const veryLongArray = sortedArray.at(0);
const finalResult: T[] = [];
veryLongArray
.map((item) => sortedArray.every((i) => i.some((s) => equalFunc(s, item))))
.forEach((result, i) => {
if (result) {
finalResult.push(veryLongArray.at(i));
}
});
return finalResult;
};
export const uniq = <T>(array: Array<T>, equalFunc: (item1: T, item2: T) => boolean = (item1, item2) => item1 === item2) => {
if (array.length === 0) return array;
const resultArray = [array.at(0)];
array.forEach((item) => {
const someEqual = resultArray.some((rItem) => equalFunc(item, rItem));
if (!someEqual) {
resultArray.push(item);
}
});
return resultArray;
};
export const sortByKey = <T>(array: Array<T>, key: keyof T): Array<T> => {
if (array.length === 0) return [];
const arrayClone = [...array];
const sortedValues = array.map((item) => item[key]).sort();
return sortedValues.map((value) => {
const index = arrayClone.findIndex((item) => (item == null ? false : item[key] === value));
const result = arrayClone[index];
arrayClone[index] = null;
return result;
});
};
export const difference = <T>(
array: Array<T>,
equalFunc: (item1: T, item2: T) => boolean = (item1, item2) => item1 === item2,
...args: T[][]
) => {
if (array.length === 0) return [];
const arrayConcat = args.flat();
const result: T[] = [];
array.forEach((item) => {
const foundItem = arrayConcat.find((diffItem) => equalFunc(item, diffItem));
if (foundItem == null) {
result.push(item);
}
});
return result;
};
export const sliceByCount = <T>(array: T[], countInArray: number): T[][] => {
const result: T[][] = [];
for (let i = 0; i < Math.ceil(array.length / countInArray); i++) {
result.push(array.slice(countInArray * i, countInArray * i + countInArray));
}
return result;
};

View file

@ -0,0 +1,6 @@
/** Clear last `/` symbol from string. Can be used for normalize URL */
export const clearLastSlash = (url: string) => (typeof url === "string" && url.endsWith("/") ? url.slice(0, -1) : url);
export * from "./number";
export * from "./array";
export * from "./string";

View file

@ -0,0 +1,22 @@
import { getMaxNumberByLength } from "./number";
describe("getMaxNumberByLength function", () => {
it("Check correct result", () => {
const result = getMaxNumberByLength(4);
expect(result).toBe(9999);
});
it("To throw error if length value more 15", () => {
expect(() => getMaxNumberByLength(16)).toThrow(
`In JS, calculating a number longer than 15 characters is a bad plan, your desire is: 16`,
);
});
it("To throw error if call with negative value", () => {
expect(() => getMaxNumberByLength(-4)).toThrow(`Have nothing to calc, -4`);
});
it("To throw error if call with negative not number value", () => {
expect(() => getMaxNumberByLength(null)).toThrow(`Have nothing to calc, null`);
});
});

View file

@ -0,0 +1,14 @@
/** Get maximum number by char count */
export const getMaxNumberByLength = (length: number): number => {
if (typeof length != "number" || length < 1) {
throw new Error(`Have nothing to calc, ${length}`);
}
if (length > 15) {
throw new Error(`In JS, calculating a number longer than 15 characters is a bad plan, your desire is: ${length}`);
}
let reducer = "9";
for (let i = length - 1; i > 0; i--) {
reducer = reducer + "9";
}
return Number(reducer);
};

View file

@ -0,0 +1,106 @@
import { capitalize, contains, isNullOrEmptyOrWhiteSpace, isString, stringEquals } from "./string";
describe("capitalize function", () => {
it("If capitalize empty string returned empty string", () => {
const result = capitalize("");
expect(result).toBe("");
});
it("If capitalize `null` value, returned null value", () => {
const value = null as unknown as string;
const result = capitalize(value);
expect(result).toBeNull();
});
it("If capitalize capitalized string, returned not changed value", () => {
const result = capitalize("Capitalize");
expect(result).toBe("Capitalize");
});
it("If capitalize lower case string, returned capitalize string", () => {
const result = capitalize("capitalize");
expect(result).toBe("Capitalize");
});
});
describe("contains function", () => {
const cases: Array<[string, string, boolean]> = [
["contains", "cont", true],
["contains", "Cont", true],
["Contains", "cont", true],
["Contains", "Cont", true],
["cOnTaIns", "CoNt", true],
["cOnTaIns", "oNt", true],
["cOnTaIns", "insa", false],
["cOnTaIns", "kz", false],
];
it("Check cases", () => {
cases.forEach(([str1, str2, expectResult]) => {
const result = contains(str1, str2);
expect(result).toBe(expectResult);
});
});
});
describe("isNullOrEmptyOrWhiteSpace function", () => {
it("Call with null value, return `true`", () => {
const result = isNullOrEmptyOrWhiteSpace(null as unknown as string);
expect(result).toBe(true);
});
it("Call with empty string value, return `true`", () => {
const result = isNullOrEmptyOrWhiteSpace("");
expect(result).toBe(true);
});
it("Call with string value with white space, return `true`", () => {
const result = isNullOrEmptyOrWhiteSpace(" ");
expect(result).toBe(true);
});
it("Call with undefined, return `true`", () => {
const result = isNullOrEmptyOrWhiteSpace(undefined as unknown as string);
expect(result).toBe(true);
});
it("Call with another value, return `false`", () => {
const result = isNullOrEmptyOrWhiteSpace("Some string");
expect(result).toBe(false);
});
});
describe("stringEquals function", () => {
it("Check positive case", () => {
const result = stringEquals("some string", "some string");
expect(result).toBe(true);
});
it("Check negative case", () => {
const result = stringEquals("Some string", "some string");
expect(result).toBe(false);
});
it("Check ignore case option", () => {
const result = stringEquals("Some string", "some string", true);
expect(result).toBe(true);
});
});
describe("isString function", () => {
const NOT_STRING_VALUES = [5, null, undefined, NaN, {}, true];
const STRING_VALUES = ["string", "", new String("Example"), new String()];
it("Negative cases", () => {
NOT_STRING_VALUES.forEach((value) => {
const result = isString(value);
expect(result).toBe(false);
});
});
it("Positive cases", () => {
STRING_VALUES.forEach((value) => {
const result = isString(value);
expect(result).toBe(true);
});
});
});

View file

@ -0,0 +1,30 @@
export function toDashedStyle(camleCase: string) {
return camleCase.replace(/([A-Z])/g, (segment) => `-${segment.toLowerCase()}`);
}
export function capitalize(word: string) {
if (word == null || word.length === 0 || /[A-Z]/.test(word[0])) return word;
return word[0].toUpperCase() + word.substr(1);
}
export function isNullOrEmptyOrWhiteSpace(str: string): boolean {
if (str == null) return true;
return str.trim() === "";
}
export function contains(str1: string, str2: string): boolean {
return str1 != null && str2 != null && str1.toLowerCase().indexOf(str2.toLowerCase()) !== -1;
}
export function stringEquals(str1: string, str2: string, ignoreCase = false): boolean {
str1 = ignoreCase ? str1.toLowerCase() : str1;
str2 = ignoreCase ? str2.toLowerCase() : str2;
return str1 === str2;
}
export function isString(value: unknown): value is string {
return Boolean(typeof value === "string" || value instanceof String);
}

80
lib/src/dom/index.ts Normal file
View file

@ -0,0 +1,80 @@
import { _switch } from "../operators";
/**
* Get CSS variable and transform to JS data types for using it in your code.
* @param name - name of css variable
* @param fallbackValue - value, if variable not found
* @param selector - a unique selector that returns a single DOM element
* @example const buttonColor = getCssVariable("--my-button-color")
*/
export const getCssVariable = (name: string, fallbackValue: string = "", selector?: string) =>
new CSSVariable(name, fallbackValue, selector);
/**
* Allow read CSS variable and transform to JS data types
*/
export class CSSVariable {
private _rawValue: string;
constructor(name: string, fallbackValue: string = "", selector?: string) {
const element = selector ? document.querySelector(selector) : document.documentElement;
const cssVar = getComputedStyle(element).getPropertyValue(name).trim();
this._rawValue = cssVar ? cssVar : fallbackValue;
}
public toBoolean(): boolean {
return _switch(this._rawValue)
.case("visible", () => true)
.case("hidden", () => false)
.result();
}
public toInt(): number {
return parseInt(this._rawValue);
}
public toFloat(): number {
return parseFloat(this._rawValue);
}
public toString(): string {
return this._rawValue;
}
public toIntArray(): number[] {
return this._rawValue === "" ? [] : this._rawValue.split(" ").map((item) => parseInt(item));
}
public toFloatArray(): number[] {
return this._rawValue === "" ? [] : this._rawValue.split(" ").map((item) => parseFloat(item));
}
public toRgba(alpha?: number): string {
const color = this._rawValue.trim();
if (/^rgba?\(/i.test(color)) {
return color;
}
let hex = color.replace(/^#/, "");
if (hex.length === 3) {
hex =
hex
.split("")
.map((c) => c + c)
.join("") + "FF";
} else if (hex.length === 6) {
hex += "FF";
} else if (hex.length !== 8) {
throw new Error("Invalid HEX-color");
}
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
const a = alpha != null ? alpha : parseInt(hex.slice(6, 8), 16) / 255;
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
}

View file

@ -0,0 +1,6 @@
export * from "./operators";
export * from "./objects";
export * from "./algorithms";
export * from "./dom";
export * from "./utils";
export * from "./web-api";

1
lib/src/objects/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./objects";

View file

@ -0,0 +1,334 @@
import {
getObjectProperty,
setObjectProperty,
removeObjectProperty,
haveObjectProperty,
addFieldsToUnion,
getProperties,
objectIntersection,
defaults,
omit,
} from "./objects";
describe("Have object property", () => {
it("Utils: test haveObjectProperty() - null/undefined argument", () => {
expect(haveObjectProperty(null, ["field"])).toBe(false);
expect(haveObjectProperty(null, ["field", "field2"])).toBe(false);
expect(haveObjectProperty(undefined, ["field"])).toBe(false);
expect(haveObjectProperty(undefined, ["field", "field2"])).toBe(false);
});
it("Utils: test haveObjectProperty() - simple object", () => {
expect(haveObjectProperty({ field: 1 }, ["field"])).toBe(true);
expect(haveObjectProperty({ field: null }, ["field"])).toBe(true);
});
it("Utils: test haveObjectProperty() - difficult object", () => {
expect(haveObjectProperty({ field: 1, field2: { subField: "subField" } }, ["field2", "subField"])).toBe(true);
expect(haveObjectProperty({ field: 1, field2: { subField: null } }, ["field2", "subField"])).toBe(true);
});
it("Primitive field", () => {
expect(haveObjectProperty({ field: 1, field2: 2 }, ["field2", "field4"])).toBe(false);
});
it("Empty sub object", () => {
const object = { a: 1, b: { c: {} } };
expect(haveObjectProperty(object, ["b", "c", "d"])).toBe(false);
});
});
describe("Add field to union", () => {
it("Utils: addFieldsToUnion() - simple object", () => {
const union = {};
const object = { a: 1, b: 2, c: 3 };
addFieldsToUnion(object, union);
expect(union.hasOwnProperty("a")).toBe(true);
expect(union.hasOwnProperty("b")).toBe(true);
expect(union.hasOwnProperty("c")).toBe(true);
});
it("Utils addFieldsToUnion() - difficult objects", () => {
const union = {};
const object = {
a: 1,
b: 2,
d: { e: 4, f: 5 },
g: { c: 3, y: { a: 1 } },
};
addFieldsToUnion(object, union);
expect(haveObjectProperty(union, ["a"])).toBe(true);
expect(haveObjectProperty(union, ["b"])).toBe(true);
expect(haveObjectProperty(union, ["d"])).toBe(true);
expect(haveObjectProperty(union, ["g"])).toBe(true);
expect(haveObjectProperty(union, ["d", "e"])).toBe(true);
expect(haveObjectProperty(union, ["d", "f"])).toBe(true);
expect(haveObjectProperty(union, ["g", "c"])).toBe(true);
expect(haveObjectProperty(union, ["g", "y", "a"])).toBe(true);
});
it("Utils addFieldsToUnion() - several objects", () => {
const union: any = {};
const object = {
a: 1,
b: 2,
d: { e: 4, f: 5 },
g: { c: 3, y: { a: 1 } },
};
const object2 = {
c: 3,
y: { u: 2 },
d: { e: 4 },
};
addFieldsToUnion(object, union);
addFieldsToUnion(object2, union);
expect(haveObjectProperty(union, ["a"])).toBe(true);
expect(haveObjectProperty(union, ["b"])).toBe(true);
expect(haveObjectProperty(union, ["d"])).toBe(true);
expect(haveObjectProperty(union, ["g"])).toBe(true);
expect(haveObjectProperty(union, ["d", "e"])).toBe(true);
expect(haveObjectProperty(union, ["d", "f"])).toBe(true);
expect(haveObjectProperty(union, ["g", "c"])).toBe(true);
expect(haveObjectProperty(union, ["g", "y", "a"])).toBe(true);
expect(haveObjectProperty(union, ["c"])).toBe(true);
expect(haveObjectProperty(union, ["y", "u"])).toBe(true);
});
});
class WithGetter {
get field() {
return 5;
}
get field2() {
return undefined;
}
}
class WithGetterExtended extends WithGetter {
get field3() {
return 3;
}
constructor() {
super();
}
}
describe("Get/Set object property by path", () => {
let obj: any;
beforeEach(() => {
obj = {
test: "string",
field: 5,
flag: true,
obj2: {
field2: null,
obj3: { field3: true },
},
};
});
it("Utils: test getItemProperty()", () => {
const value = getObjectProperty(obj, ["test"]);
expect(value).toBe(obj.test);
const value2 = getObjectProperty(obj, ["obj2", "obj3", "field3"]);
expect(value2).toBe(obj.obj2.obj3.field3);
});
it("Get property from not existing path", () => {
expect(() => getObjectProperty(obj, ["notExistField", "field"])).toThrow();
});
it("Get property from getter", () => {
const instance1 = new WithGetter();
const value = getObjectProperty(instance1, ["field"]);
expect(value).toBe(5);
const value2 = getObjectProperty(instance1, ["field2"]);
expect(value2).toBe(undefined);
const instance2 = new WithGetterExtended();
const value3 = getObjectProperty(instance2, ["field3"]);
expect(value3).toBe(3);
});
it("Utils: test setItemProperty()", () => {
const path1 = ["flag"];
setObjectProperty(obj, path1, false);
expect(obj.flag).toBe(false);
expect(path1.length).toBe(1);
const path2 = ["obj2", "obj3", "field3"];
setObjectProperty(obj, path2, false);
expect(path2.length).toBe(3);
expect(obj.obj2.obj3.field3).toBe(false);
const path3 = ["obj2", "obj3", "obj4", "field4"];
setObjectProperty(obj, path3, 10, true);
expect(obj.obj2.obj3.obj4.field4).toBe(10);
expect(path3.length).toBe(4);
});
});
describe("Remove object property", () => {
it("Utils: removeObjectProperty() - simple object", () => {
const object = { a: 1, b: 2, c: 3 };
removeObjectProperty(object, ["a"]);
expect(haveObjectProperty(object, ["a"])).toBe(false);
expect(haveObjectProperty(object, ["b"])).toBe(true);
expect(haveObjectProperty(object, ["c"])).toBe(true);
});
it("Utils: removeObjectProperty() - difficult objects", () => {
const object = { a: 1, b: { c: { d: 5 } } };
removeObjectProperty(object, ["b", "c", "d"]);
expect(haveObjectProperty(object, ["b", "c", "d"])).toBe(false);
expect(haveObjectProperty(object, ["b", "c"])).toBe(true);
expect(haveObjectProperty(object, ["b"])).toBe(true);
expect(haveObjectProperty(object, ["a"])).toBe(true);
});
it("Utils: removeObjectProperty() - throw exception if wrong path", () => {
const object = { a: 1 };
expect(() => removeObjectProperty(object, ["c", "d"])).toThrow();
});
});
describe("Get properties", () => {
it("null argument", () => {
const result = getProperties(null);
expect(result.length).toBe(0);
});
it("undefined argument", () => {
const result = getProperties(undefined);
expect(result.length).toBe(0);
});
it("Simple object", () => {
const obj: any = { a: "1", b: "1", c: "1" };
const result = getProperties(obj);
expect(result.length).toBe(3);
expect(result.flat().includes("a")).toBe(true);
expect(result.flat().includes("b")).toBe(true);
expect(result.flat().includes("c")).toBe(true);
});
it("Null field in object", () => {
const obj: any = { a: "1", b: { c: null } };
const result = getProperties(obj);
expect(result[2][0]).toBe("b");
expect(result[2][1]).toBe("c");
});
it("Undefined field in object", () => {
const obj: any = { a: "1", b: { c: undefined } };
const result = getProperties(obj);
expect(result[2][0]).toBe("b");
expect(result[2][1]).toBe("c");
});
});
describe("Intersection", () => {
it("Utils: objectIntersection() - test simple objects", () => {
const objects = [
{ field1: 1, field2: 2, field3: 3 },
{ field1: 1, field3: 3, field4: 4 },
];
const result = objectIntersection<object>(...objects);
expect(result.hasOwnProperty("field1")).toBe(true);
expect(result.hasOwnProperty("field2")).toBe(false);
expect(result.hasOwnProperty("field3")).toBe(true);
expect(result.hasOwnProperty("field4")).toBe(false);
});
it("Utils: objectIntersection() - test difficult objects", () => {
const objects = [
{ field1: 1, field2: 2, field3: 3, field4: { subField1: 2, subField2: 3, subs: null } },
{ field1: 1, field3: 3, field4: 4 },
{ field2: 3, field3: 3, field4: { subField1: 2, subField3: { subSubField1: 5, subSubField2: 2 } } },
];
const result = objectIntersection(...objects);
expect(result.hasOwnProperty("field3")).toBe(true);
expect(result.hasOwnProperty("field4")).toBe(true);
expect(result.hasOwnProperty("field1")).toBe(false);
});
it("Utils: objectIntersection() - various data types of field", () => {
const objects = [
{ field1: 1, field2: 2 },
{ field1: "1", field2: "2", field3: "3" },
];
const result = objectIntersection(...objects);
expect(result.hasOwnProperty("field1")).toBe(true);
expect(result.hasOwnProperty("field2")).toBe(true);
expect(result.hasOwnProperty("field3")).toBe(false);
});
it("Utils: objectIntersection() - test primitives", () => {
expect(() => objectIntersection<object>(1, "string", false)).toThrow(new Error("Argument can not be primitive!"));
expect(() => objectIntersection<object>("string", false, 1)).toThrow(new Error("Argument can not be primitive!"));
expect(() => objectIntersection<object>(false, 1, "string")).toThrow(new Error("Argument can not be primitive!"));
});
it("Utils: objectIntersection() - test call with null/undefined", () => {
expect(() => objectIntersection({ a: 1 }, null)).toThrow(new Error("Argument can not be null/undefined!"));
});
it("Utils: objectIntersection() - test once argument", () => {
const object = { field1: 1, field2: 2, field3: 3 };
const result = objectIntersection<object>(object);
expect(result.hasOwnProperty("field1")).toBe(true);
expect(result.hasOwnProperty("field2")).toBe(true);
expect(result.hasOwnProperty("field3")).toBe(true);
});
});
describe("defaults function", () => {
it("Call with null and null", () => {
const result = defaults(null, null);
expect(result).toEqual({});
});
it("Call with {} and null", () => {
const result = defaults({}, null);
expect(result).toEqual({});
});
it("Call with {} and {}", () => {
const result = defaults({}, {});
expect(result).toEqual({});
});
it("Call with object and null", () => {
const result = defaults({ a: "a", b: "b" }, null);
expect(result).toEqual({ a: "a", b: "b" });
});
it("Source have identical fields", () => {
const result = defaults({ a: "a", b: "b" }, { a: "a1", b: "b1", c: "c1" });
expect(result).toEqual({ a: "a", b: "b", c: "c1" });
});
it("Object have field with null value", () => {
const result = defaults({ a: "a", b: null }, { a: "a1", b: "b1", c: "c1" });
expect(result).toEqual({ a: "a", b: "b1", c: "c1" });
});
it("Object have felid with undefined value", () => {
const result = defaults({ a: "a", b: undefined }, { a: "a1", b: "b1", c: "c1" });
expect(result).toEqual({ a: "a", b: "b1", c: "c1" });
});
it("Source have field with null value", () => {
const result = defaults({ a: "a", b: "b" }, { a: "a1", b: null, c: "c1" });
expect(result).toEqual({ a: "a", b: "b", c: "c1" });
});
});
describe("omit function", () => {
it("", () => {
const obj = { a: "a", b: "b", c: "c", d: "d" };
const result = omit(obj);
expect(result).toBe(obj);
});
});

187
lib/src/objects/objects.ts Normal file
View file

@ -0,0 +1,187 @@
//#region get/set object property
export function getPropertyDescriptor(obj: any, prop: string): PropertyDescriptor {
let desc: PropertyDescriptor;
do {
if (obj == null) return {};
desc = Object.getOwnPropertyDescriptor(obj, prop);
obj = Object.getPrototypeOf(obj);
} while (!desc);
return desc;
}
const haveObjectField = (object: object, property: string) => {
if (object == null) return false;
const haveField = {}.hasOwnProperty.call(object, property);
const fieldDesc = getPropertyDescriptor(object, property);
const haveGetFn = !!fieldDesc.get;
return haveField || haveGetFn;
};
/** Get object property by path. If path not exist throw error. */
export function getObjectProperty<T = any>(object: object, path: string[]): T {
if (object == null) return null;
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < path.length; i++) {
const segment = path[i];
if (!haveObjectField(object, segment)) throw new Error(`Can not read property ${segment} from object!`);
object = object[segment];
}
return object as any;
}
/** Set object property value by path. */
export function setObjectProperty<T = any>(object: object, path: string[], value: T, force?: boolean) {
path = [...path];
if (path.length === 0) return;
const finalKey = path.pop() as string;
for (const key of path) {
if (object[key] == null && force) object[key] = {};
object = object[key];
if (object == null && !force) return;
}
object[finalKey] = value;
}
//#endregion
export function removeObjectProperty(object: object, path: string[]): void {
if (object == null || path == null) return;
const pathWithoutLast = [...path];
const lastKey = pathWithoutLast.pop();
if (pathWithoutLast.length === 0) {
delete object[lastKey];
} else {
try {
const obj = getObjectProperty(object, pathWithoutLast);
delete obj[lastKey];
} catch {
throw new Error(`No property by path /${path.join("/")}!`);
}
}
}
export function haveObjectProperty(object: object, path: string[]): boolean {
if (object == null) return false;
try {
getObjectProperty(object, path);
return true;
} catch {
return false;
}
}
export function objectsDiff(objects: [object, object]) {
const [object1, object2] = objects;
Object.keys(object1).forEach((field) => {
if (Array.isArray(object1[field])) return;
if (typeof object1[field] === "object" && object1[field] != null) {
objectsDiff([object1[field], object2[field] ?? {}]);
} else {
if (object1[field] === object2[field]) delete object1[field];
if (object1[field] === undefined) delete object1[field];
}
});
}
export function getProperties(object: any): string[][] {
if (object == null) return [];
const result: string[][] = [];
Object.keys(object).forEach((key) => {
result.push([key]);
const field = object[key];
if (typeof field === "object" && !Array.isArray(field) && field != null) {
result.push(...getProperties(object[key]).map((path) => [key, ...path]));
}
});
return result;
}
//#region add/delete field to union
export function addFieldsToUnion(obj: any, union: any) {
const fields = getProperties(obj);
fields.forEach((path) => {
try {
const value = getObjectProperty(union, path);
if (!value) setObjectProperty(union, path, null, true);
} catch {
setObjectProperty(union, path, null, true);
}
});
}
//#endregion
export function mapEmptyFields(obj: object, f: (obj: any, field: string) => void) {
Object.keys(obj).forEach((field) => {
if (typeof obj[field] === "object" && obj[field] != null) {
const keys = Object.keys(obj[field]);
if (keys.length === 0) {
f(obj, field);
mapEmptyFields(obj, f);
} else {
mapEmptyFields(obj[field], f);
}
}
});
}
export function objectIntersection<T = any>(...objects: any): T {
const intersection = {};
const objArr = objects as any[];
objArr.forEach((obj) => {
if (typeof obj !== "object") throw new Error("Argument can not be primitive!");
if (obj == null) throw new Error("Argument can not be null/undefined!");
getProperties(obj).forEach((path) => {
const haveInAll = objArr
.filter((o) => o !== obj)
.map((o) => haveObjectProperty(o, path))
.reduce((p, n) => p && n, true);
if (haveInAll) setObjectProperty(intersection, path, null, true);
});
});
return intersection as T;
}
/** Deep merge objects */
export const deepMerge = <T>(target: T, source: T): T => {
const isObject = (obj) => obj && typeof obj === "object";
if (!isObject(target) || !isObject(source)) {
return source as T;
}
Object.keys(source).forEach((key) => {
const targetValue = target[key];
const sourceValue = source[key];
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
target[key] = sourceValue;
} else if (isObject(targetValue) && isObject(sourceValue)) {
target[key] = deepMerge(Object.assign({}, targetValue), sourceValue);
} else {
target[key] = sourceValue;
}
});
return target as T;
};
export const defaults = <TObject, TSource>(object: TObject, source: TSource): TSource & TObject => {
if (object == null) object = {} as TObject;
if (source == null) return object as TSource & TObject;
Object.keys(source).forEach((key) => {
if (object[key] == null) {
object[key] = source[key];
}
});
return object as TSource & TObject;
};
export const omit = <TObject>(object: TObject, ...args: string[]) => {
if (args.length === 0) return object;
const result = {};
Object.keys(object).forEach((key) => {
if (args.includes(key)) return;
result[key] = object[key];
});
return result;
};

View file

@ -0,0 +1,10 @@
export * from "./switch-case";
/** Alternate for `try { ... } catch { ... }` statement */
export const evade = <T>(fn: () => T, def?: T) => {
try {
return fn();
} catch {
return def;
}
};

View file

@ -0,0 +1,53 @@
class Switch<V = any, R = any> {
protected _complete = false;
protected _result: R;
constructor(protected _value: V) {}
public case(isValue: V, actions: () => R, isBreak: boolean = true) {
if (!this._complete) {
if (this._value === isValue) {
this._result = actions();
if (isBreak) this._complete = true;
}
}
return this;
}
public default(actions: () => R) {
if (!this._complete) {
this._result = actions();
}
this._complete = true;
return { result: () => this.result() };
}
public result() {
return this._result;
}
}
class SwitchInstance<R = any> extends Switch {
constructor(_value: any) {
super(_value);
}
public case(type: new (...args: any) => any, actions: () => R, isBreak: boolean = true) {
if (!this._complete) {
if (this._value instanceof type) {
this._result = actions();
if (isBreak) this._complete = true;
}
}
return this;
}
}
// eslint-disable-next-line no-underscore-dangle
export const _switch = <V = any, R = any>(value: V) => new Switch<V, R>(value);
export const _switchInstance = <R = any>(value: any) => new SwitchInstance<R>(value);

View file

@ -1,6 +1,6 @@
{
"name": "@gxc-solutions/lib",
"version": "0.0.1",
"name": "@gxc-solutions/lett-js",
"version": "1.0.0",
"main": "index.js",
"author": "GXC Solutions",
"publishConfig": {

View file

@ -0,0 +1,3 @@
# Description
Algorithms, utils, browser API wrappers and other useful functions for JavaScript and TypeScript.

View file

@ -0,0 +1,31 @@
export class BlobReader {
static readAsDataUrl(blob: Blob | File): Promise<string> {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.onabort = () => reject(new Error("Read aborted"));
reader.readAsDataURL(blob);
});
}
static readAsArrayBuffer(blob: Blob | File): Promise<ArrayBuffer> {
return new Promise<ArrayBuffer>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as ArrayBuffer);
reader.onerror = () => reject(reader.error);
reader.onabort = () => reject(new Error("Read aborted"));
reader.readAsArrayBuffer(blob);
});
}
static readAsText(blob: Blob | File): Promise<string> {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.onabort = () => reject(new Error("Read aborted"));
reader.readAsText(blob);
});
}
}

1
lib/src/utils/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./blob-reader";

View file

@ -0,0 +1,25 @@
export class Deferred<T> {
private readonly _promise: Promise<T>;
private _resolve: (value?: T | PromiseLike<T>) => void;
private _reject: <E>(error?: E) => void;
get promise(): Promise<T> {
return this._promise;
}
constructor() {
this._promise = new Promise<T>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
public resolve(value?: T | PromiseLike<T>): void {
this?._resolve(value);
}
public reject<E>(error?: E): void {
this?._reject(error);
}
}

3
lib/src/web-api/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from "./thread";
export * from "./deferred";
export * from "./uuidv4";

27
lib/src/web-api/thread.ts Normal file
View file

@ -0,0 +1,27 @@
export class Thread<R, F extends (...args: any[]) => R> {
private _worker: Worker;
constructor(fn: F) {
const stringFn = `self.onmessage = (event) => {
const data = event.data;
const fn = ${fn.toString()};
const result = fn(...data.data);
self.postMessage(result);
}`;
const jsFile = new File([stringFn], `${fn.name}.js`, { type: "application/javascript" });
const jsFileUrl = URL.createObjectURL(jsFile);
this._worker = new Worker(jsFileUrl);
}
public run(...args: Parameters<F>): Promise<R> {
this._worker.postMessage({ data: [...args] });
return new Promise<R>((res, rej) => {
this._worker.addEventListener("message", (event) => res(event.data));
this._worker.addEventListener("error", (event) => rej(event));
});
}
public terminate(): void {
this._worker.terminate();
}
}

10
lib/src/web-api/uuidv4.ts Normal file
View file

@ -0,0 +1,10 @@
export const uuidv4 = () => {
if (crypto.randomUUID == null) {
"10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
// eslint-disable-next-line no-bitwise
(+c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (+c / 4)))).toString(16),
);
} else {
return crypto.randomUUID();
}
};