@etsoo/shared
Version:
TypeScript shared utilities and functions
546 lines (545 loc) • 17.7 kB
JavaScript
/**
* Generic object type
* Narrow case, uses StringRecord
* Before was wrong with {}, from 4.8 unknown = {} | null | undefined
*/
BigInt.prototype.toJSON = function () {
return this.toString() + "n";
};
/**
* Interface data types
*/
export var DataTypes;
(function (DataTypes) {
/**
* Basic type and basic type array names array
*/
DataTypes.BasicArray = [
"number",
"number[]",
"bigint",
"bigint[]",
"date",
"date[]",
"boolean",
"boolean[]",
"string",
"string[]",
"unknown[]"
];
/**
* Simple type enum
*/
let SimpleEnum;
(function (SimpleEnum) {
SimpleEnum[SimpleEnum["Number"] = 1] = "Number";
SimpleEnum[SimpleEnum["Bigint"] = 2] = "Bigint";
SimpleEnum[SimpleEnum["Date"] = 3] = "Date";
SimpleEnum[SimpleEnum["Boolean"] = 4] = "Boolean";
SimpleEnum[SimpleEnum["String"] = 5] = "String";
SimpleEnum[SimpleEnum["Array"] = 9] = "Array";
})(SimpleEnum = DataTypes.SimpleEnum || (DataTypes.SimpleEnum = {}));
/**
* Extended type enum
*/
let ExtendedEnum;
(function (ExtendedEnum) {
ExtendedEnum[ExtendedEnum["Unkwown"] = 0] = "Unkwown";
ExtendedEnum[ExtendedEnum["Int"] = 10] = "Int";
ExtendedEnum[ExtendedEnum["Money"] = 11] = "Money";
ExtendedEnum[ExtendedEnum["IntMoney"] = 12] = "IntMoney";
ExtendedEnum[ExtendedEnum["DateTime"] = 13] = "DateTime";
ExtendedEnum[ExtendedEnum["Email"] = 21] = "Email";
ExtendedEnum[ExtendedEnum["Phone"] = 22] = "Phone";
ExtendedEnum[ExtendedEnum["URL"] = 23] = "URL";
ExtendedEnum[ExtendedEnum["Logo"] = 24] = "Logo";
})(ExtendedEnum = DataTypes.ExtendedEnum || (DataTypes.ExtendedEnum = {}));
/**
* Combined type enum
*/
DataTypes.CombinedEnum = {
...SimpleEnum,
...ExtendedEnum
};
/**
* Horizontal align enum
*/
let HAlignEnum;
(function (HAlignEnum) {
HAlignEnum[HAlignEnum["Left"] = 1] = "Left";
HAlignEnum[HAlignEnum["Center"] = 2] = "Center";
HAlignEnum[HAlignEnum["Right"] = 3] = "Right";
})(HAlignEnum = DataTypes.HAlignEnum || (DataTypes.HAlignEnum = {}));
/**
* Vertical align enum
*/
let VAlignEnum;
(function (VAlignEnum) {
VAlignEnum[VAlignEnum["Top"] = 1] = "Top";
VAlignEnum[VAlignEnum["Center"] = 2] = "Center";
VAlignEnum[VAlignEnum["Bottom"] = 3] = "Bottom";
})(VAlignEnum = DataTypes.VAlignEnum || (DataTypes.VAlignEnum = {}));
/**
* Placement enum
*/
let PlacementEnum;
(function (PlacementEnum) {
PlacementEnum[PlacementEnum["TopLeft"] = 0] = "TopLeft";
PlacementEnum[PlacementEnum["TopCenter"] = 1] = "TopCenter";
PlacementEnum[PlacementEnum["TopRight"] = 2] = "TopRight";
PlacementEnum[PlacementEnum["MiddleLeft"] = 3] = "MiddleLeft";
PlacementEnum[PlacementEnum["Center"] = 4] = "Center";
PlacementEnum[PlacementEnum["MiddleRight"] = 5] = "MiddleRight";
PlacementEnum[PlacementEnum["BottomLeft"] = 6] = "BottomLeft";
PlacementEnum[PlacementEnum["BottomCenter"] = 7] = "BottomCenter";
PlacementEnum[PlacementEnum["BottomRight"] = 8] = "BottomRight";
PlacementEnum[PlacementEnum["Unknown"] = 9] = "Unknown"; // Reserved for modal, only one instance held at the same time
})(PlacementEnum = DataTypes.PlacementEnum || (DataTypes.PlacementEnum = {}));
/**
* Tristate enum
* 三态枚举
*/
let TristateEnum;
(function (TristateEnum) {
/**
* False
* 假
*/
TristateEnum[TristateEnum["False"] = 0] = "False";
/**
* True
* 真
*/
TristateEnum[TristateEnum["True"] = 1] = "True";
/**
* Unsure
* 无法判断
*/
TristateEnum[TristateEnum["Unsure"] = 9] = "Unsure";
})(TristateEnum = DataTypes.TristateEnum || (DataTypes.TristateEnum = {}));
/**
* Convert value to target type
* @param input Input value
* @param target Target type
* @returns Converted value
*/
function convert(input, target) {
// null or undefined
if (input == null)
return undefined;
// Array
if (Array.isArray(target)) {
// Element item
const elementItem = target.length > 0 ? target[0] : input;
const elementType = getBasicNameByValue(elementItem, true);
if (elementType == null)
return input;
return convertByType(input, elementType);
}
// Target type
const targetType = getBasicNameByValue(target, false);
if (targetType == null) {
if (typeof input === typeof target)
return input;
return undefined;
}
return convertByType(input, targetType);
}
DataTypes.convert = convert;
/**
* Convert by type name like 'string'
* @param input Input value
* @param targetType Target type
* @returns Converted value
*/
function convertByType(input, targetType) {
// null or undefined
// And avoid empty string to mass up in different type
if (input == null || (typeof input === "string" && input.trim() === ""))
return undefined;
// Array
if (targetType.endsWith("[]")) {
// Input array
const inputArray = Array.isArray(input)
? input
: typeof input === "string"
? input.split(/,\s*/g) // Support comma separated array
: [input];
// Element type
const elementType = (targetType.slice(0, targetType.length - 2));
// Convert type
return (inputArray
.map((item) => convertByType(item, elementType))
.filter((item) => item != null) // Remove undefined item
);
}
// Same type
if (typeof input === targetType)
return input;
// Date
if (targetType === "date") {
if (input instanceof Date)
return input;
if (typeof input === "string" || typeof input === "number") {
const date = new Date(input);
return date == null ? undefined : date;
}
return undefined;
}
// Bigint
if (targetType === "bigint") {
if (typeof input === "string" ||
typeof input === "number" ||
typeof input === "boolean")
return BigInt(input);
return undefined;
}
// Boolean
if (targetType === "boolean") {
if (typeof input === "string" || typeof input === "number") {
// Here are different with official definition
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
if (input === "0" || input === "false")
return false;
return Boolean(input);
}
return undefined;
}
// Number
if (targetType === "number") {
const number = Number(input);
return isNaN(number) ? undefined : number;
}
// String
if (targetType === "string") {
return String(input);
}
// Default
return undefined;
}
DataTypes.convertByType = convertByType;
/**
* Convert value to target enum type
* @param input Input value
* @param enumType Target enum type
* @returns Converted type
*/
function convertSimple(input, enumType) {
const type = getBasicName(enumType);
const value = convertByType(input, type);
if (value == null)
return undefined;
if (typeof value === "number") {
if (enumType === DataTypes.CombinedEnum.Int || enumType === DataTypes.CombinedEnum.IntMoney)
return Math.round(value);
if (enumType === DataTypes.CombinedEnum.Money)
return Math.round(10000 * value) / 10000;
}
return value;
}
DataTypes.convertSimple = convertSimple;
/**
* Get basic type name from Enum type
* @param enumType Enum type
* @returns Basic type name result
*/
function getBasicName(enumType) {
switch (enumType) {
case DataTypes.CombinedEnum.Array:
return "unknown[]";
case DataTypes.CombinedEnum.Bigint:
return "bigint";
case DataTypes.CombinedEnum.Boolean:
return "boolean";
case DataTypes.CombinedEnum.Date:
case DataTypes.CombinedEnum.DateTime:
return "date";
case DataTypes.CombinedEnum.Number:
case DataTypes.CombinedEnum.Int:
case DataTypes.CombinedEnum.IntMoney:
case DataTypes.CombinedEnum.Money:
return "number";
default:
return "string";
}
}
DataTypes.getBasicName = getBasicName;
/**
* Get value's basic type name
* @param value Input value
* @param isArray Is array
* @returns Value's basic type name
*/
function getBasicNameByValue(value, isArray = false) {
// null or undefined
if (value == null)
return undefined;
// Date
if (value instanceof Date) {
return isArray ? "date[]" : "date";
}
// No array
// Other cases
const valueType = typeof value;
const typeName = isArray ? valueType + "[]" : valueType;
if (!isBasicName(typeName))
return undefined;
return typeName;
}
DataTypes.getBasicNameByValue = getBasicNameByValue;
/*
enum Gender {
Male = "M",
Female = "F"
}
*/
/**
* Get enum item from key
* getEnumByKey(Gender, "Male") === Gender.Male
* @param enumItem Enum
* @param key Key
* @returns Enum item
*/
function getEnumByKey(enumItem, key) {
if (key in enumItem)
return enumItem[key];
return undefined;
}
DataTypes.getEnumByKey = getEnumByKey;
/**
* Get enum item from value
* getEnumByKey(Gender, "M") === Gender.Male
* @param enumItem Enum
* @param value Key
* @returns Enum item or undefined
*/
function getEnumByValue(enumItem, value) {
if (typeof value === "number") {
if (value in enumItem)
return value;
}
else {
const keys = Object.keys(enumItem);
for (const key of keys) {
const kv = enumItem[key];
if (kv === value)
return kv;
}
}
return undefined;
}
DataTypes.getEnumByValue = getEnumByValue;
/**
* Get enum string literal type value
* getEnumKey(Gender, "F") === "Female"
* @param enumItem Enum item
* @param value Value
* @returns Result
*/
function getEnumKey(enumItem, value) {
if (typeof value === "number") {
if (value in enumItem)
return enumItem[value].toString();
}
else {
const keys = Object.keys(enumItem);
for (const key of keys) {
if (enumItem[key] === value)
return key;
}
}
return undefined;
}
DataTypes.getEnumKey = getEnumKey;
/**
* Get Enum keys
* @param input Input Enum
* @returns Keys
*/
function getEnumKeys(input) {
return Object.keys(input)
.filter((key) => !/^\d+$/.test(key))
.map((item) => item);
}
DataTypes.getEnumKeys = getEnumKeys;
/**
* Get ListType2 item label
* @param item Item
* @returns Result
*/
function getListItemLabel(item) {
return "label" in item
? item.label
: "name" in item
? item.name
: item.title;
}
DataTypes.getListItemLabel = getListItemLabel;
/**
* Get object item label
* @param item Item
* @returns Result
*/
function getObjectItemLabel(item) {
return "label" in item
? `${item.label}`
: "name" in item
? `${item.name}`
: "title" in item
? `${item.title}`
: `${item}`;
}
DataTypes.getObjectItemLabel = getObjectItemLabel;
/**
* Get object field value
* @param data Data
* @param key Property name
* @returns Value
*/
function getValue(data, key) {
if (data != null && typeof key === "string" && key in data) {
return Reflect.get(data, key);
}
return undefined;
}
DataTypes.getValue = getValue;
/**
* Get object id field value
* @param data Data
* @param key Property name
* @returns Id value
*/
function getIdValue(data, key) {
return data[key];
}
DataTypes.getIdValue = getIdValue;
/**
* Get object id field value 1
* @param data Data
* @param key Property name
* @returns Id value
*/
function getIdValue1(data, key) {
const value = getValue(data, key);
if (value == null)
return undefined;
if (typeof value === "number")
return value;
return `${value}`;
}
DataTypes.getIdValue1 = getIdValue1;
/**
* Get object string field value
* @param data Data
* @param key Property name
* @returns String value
*/
function getStringValue(data, key) {
const value = getValue(data, key);
if (value == null)
return undefined;
if (typeof value === "string")
return value;
return `${value}`;
}
DataTypes.getStringValue = getStringValue;
/**
* Check the type is a basic type or not (type guard)
* @param name Type name
* @returns Is basic type
*/
function isBasicName(name) {
return DataTypes.BasicArray.includes(name);
}
DataTypes.isBasicName = isBasicName;
/**
* Is the target a simple object (Type guard)
* @param input Test data
* @param includeArray Include array as simple type
* @returns Result
*/
function isSimpleObject(input, includeArray = true) {
return (typeof input === "object" &&
input != null &&
Object.values(input).every((value) => isSimpleType(value, includeArray)));
}
DataTypes.isSimpleObject = isSimpleObject;
/**
* Is the input value simple type, include null and undefined
* @param input Input value
* @param includeArray Is array included, first non null element shoud also be basic type
*/
function isSimpleType(input, includeArray = true) {
// null & undefined
if (input == null)
return true;
// Date
if (input instanceof Date)
return true;
// Array
if (Array.isArray(input)) {
if (includeArray) {
return isSimpleType(input.find((item) => item != null));
}
else {
// No array needed
return false;
}
}
// Other cases
const type = typeof input;
if (type === "function" || type === "object" || type === "symbol")
return false;
return true;
}
DataTypes.isSimpleType = isSimpleType;
/**
* JSON.stringify replacer with full path
* https://stackoverflow.com/questions/61681176/json-stringify-replacer-how-to-get-full-path
*/
function jsonReplacer(replacer) {
const m = new Map();
return function (key, value) {
const path = m.get(this) + (Array.isArray(this) ? `[${key}]` : "." + key);
if (value === Object(value))
m.set(value, path);
return replacer.call(this, key, value, path.replace(/undefined\.\.?/, ""));
};
}
DataTypes.jsonReplacer = jsonReplacer;
/**
* JSON.stringify receiver for bigint
* @param args Keys or paths to convert to bigint
* @returns JSON receiver function
*/
function jsonBigintReceiver(...args) {
return jsonReplacer(function (key, value, path) {
if ((args.includes(key) || args.includes(path)) &&
typeof value === "string") {
if (value.endsWith("n"))
return BigInt(value.slice(0, -1));
else
return BigInt(value);
}
return value;
});
}
DataTypes.jsonBigintReceiver = jsonBigintReceiver;
/**
* JSON serialize with options
* @param obj Object to serialize
* @param options Options to ignore null or empty values
* @returns Result
*/
function jsonSerialize(obj, options) {
const { ignoreNull = true, ignoreEmpty = false } = options ?? {};
return JSON.stringify(obj, (_key, value) => {
if (ignoreNull && value == null)
return undefined;
if (ignoreEmpty && value === "")
return undefined;
return value;
});
}
DataTypes.jsonSerialize = jsonSerialize;
})(DataTypes || (DataTypes = {}));