generated from gxc-solutions/gxc-template-repo
Added first files
This commit is contained in:
parent
3be22f1023
commit
aad2fe13e7
29 changed files with 5020 additions and 8 deletions
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gxc-math-${{ github.sha }}
|
||||
name: gxc-lett-js-${{ github.sha }}
|
||||
path: ./dist/
|
||||
|
||||
deploy:
|
||||
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: gxc-math-${{ github.sha }}
|
||||
name: gxc-lett-js-${{ github.sha }}
|
||||
path: ./artifact
|
||||
|
||||
- name: Setup Node.js
|
||||
|
|
|
|||
13
jest.config.js
Normal file
13
jest.config.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/** @type {import('ts-jest').JestConfigWithTsJest} **/
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
testEnvironment: "node",
|
||||
transform: {
|
||||
"^.+.ts?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
tsconfig: "tsconfig.test.json",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
133
lib/src/algorithms/array.test.ts
Normal file
133
lib/src/algorithms/array.test.ts
Normal 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
102
lib/src/algorithms/array.ts
Normal 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;
|
||||
};
|
||||
6
lib/src/algorithms/index.ts
Normal file
6
lib/src/algorithms/index.ts
Normal 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";
|
||||
22
lib/src/algorithms/number.spec.ts
Normal file
22
lib/src/algorithms/number.spec.ts
Normal 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`);
|
||||
});
|
||||
});
|
||||
14
lib/src/algorithms/number.ts
Normal file
14
lib/src/algorithms/number.ts
Normal 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);
|
||||
};
|
||||
106
lib/src/algorithms/string.test.ts
Normal file
106
lib/src/algorithms/string.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
30
lib/src/algorithms/string.ts
Normal file
30
lib/src/algorithms/string.ts
Normal 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
80
lib/src/dom/index.ts
Normal 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})`;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
1
lib/src/objects/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./objects";
|
||||
334
lib/src/objects/objects.test.ts
Normal file
334
lib/src/objects/objects.test.ts
Normal 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
187
lib/src/objects/objects.ts
Normal 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;
|
||||
};
|
||||
10
lib/src/operators/index.ts
Normal file
10
lib/src/operators/index.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
53
lib/src/operators/switch-case.ts
Normal file
53
lib/src/operators/switch-case.ts
Normal 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);
|
||||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# Description
|
||||
|
||||
Algorithms, utils, browser API wrappers and other useful functions for JavaScript and TypeScript.
|
||||
31
lib/src/utils/blob-reader.ts
Normal file
31
lib/src/utils/blob-reader.ts
Normal 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
1
lib/src/utils/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./blob-reader";
|
||||
25
lib/src/web-api/deferred.ts
Normal file
25
lib/src/web-api/deferred.ts
Normal 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
3
lib/src/web-api/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./thread";
|
||||
export * from "./deferred";
|
||||
export * from "./uuidv4";
|
||||
27
lib/src/web-api/thread.ts
Normal file
27
lib/src/web-api/thread.ts
Normal 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
10
lib/src/web-api/uuidv4.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
3811
package-lock.json
generated
3811
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "template-of-lib-repo",
|
||||
"name": "gxc-lett-js",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
@ -12,12 +12,14 @@
|
|||
"prettier:fix": "prettier --write \"./lib/src/**/*.*\"",
|
||||
"lint": "eslint lib/src/**/*.ts",
|
||||
"prepare": "husky",
|
||||
"test": "echo test"
|
||||
"test": "jest ./lib/src",
|
||||
"test:watch": "jest ./lib/src --watch"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@eslint/eslintrc": "^3.3.4",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/node": "^25.3.3",
|
||||
|
|
@ -25,8 +27,10 @@
|
|||
"eslint": "^10.0.2",
|
||||
"globals": "^17.4.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.8.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-jest": "^29.2.4",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1"
|
||||
}
|
||||
|
|
|
|||
4
tsconfig.test.json
Normal file
4
tsconfig.test.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["**/*.test.ts", "**/*.d.ts"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue