import { ICollection } from "../models/utility/definitions" export type KArrayType = KNumberType | KBigIntType export type KGroupType = KNumberGroupType | KBigIntGroupType export type KType = KArrayType | KGroupType | "str" | "bin" | "ip4" | "bool" export type KTypeExtended = KType | null | "kignore" export type TypeForKItem = number | string | bigint | BigIntProxy | boolean | Buffer | number[] | bigint[] | boolean[] | BufferArray | NumberGroup export type TypeForKObject = T extends TypeForKItem ? never : T export type TypeForKArray = number[] | bigint[] | BufferArray export type KKey = keyof T & ( T extends string ? Exclude : T extends Buffer ? Exclude : T extends boolean ? Exclude : T extends number[] | bigint[] | boolean[] ? Exclude : T extends any[] ? Exclude | number : T extends number ? Exclude : T extends bigint | BigIntProxy ? Exclude : T extends BufferArray ? Exclude : T extends NumberGroup ? Exclude> : keyof T) export type KTypeConvert = T extends string ? "str" : T extends Buffer ? "bin" : T extends number ? KNumberType | "ip4" | "bool" : T extends bigint | BigIntProxy ? KBigIntType : T extends boolean | boolean[] ? "bool" : T extends number[] ? KNumberType : // KARRAY T extends bigint[] ? KBigIntType : // KARRAY T extends NumberGroup ? KNumberGroupType : T extends NumberGroup ? KBigIntGroupType : T extends BufferArray ? "u8" | "s8" : never export type KArrayTypeConvert = T extends Buffer ? "s8" | "u8" : T extends number[] ? KNumberType : T extends bigint[] ? KBigIntType : never export type KTypeConvertBack = TKType extends "str" ? string : TKType extends "bin" ? { type: "Buffer"; data: number[] } : TKType extends "s8" | "u8" ? [number] | number[] | { type: "Buffer"; data: number[] } : TKType extends KNumberType ? [number] | number[] : TKType extends KBigIntType ? [bigint] | bigint[] : TKType extends KNumberGroupType ? number[] : TKType extends KBigIntGroupType ? bigint[] : unknown export type NumberGroup = { "@numberGroupValue": T } export const NumberGroup = (ng: T) => { "@numberGroupValue": ng } export function isNumberGroup(value: any): value is NumberGroup { try { return Array.isArray(BigInt(value["@numberGroupValue"])) } catch { return false } } export type BufferArray = { "@bufferArrayValue": Buffer } export const BufferArray = (ba: Buffer) => { "@bufferArrayValue": ba } export function isBufferArray(value: any): value is BufferArray { try { return value["@bufferArrayValue"] instanceof Buffer } catch { return false } } export type BigIntProxy = { "@serializedBigInt": string } export const BigIntProxy = (value: bigint) => { "@serializedBigInt": value.toString() } export function isBigIntProxy(value: any): value is BigIntProxy { try { return BigInt(value["@serializedBigInt"]).toString() == value["@serializedBigInt"] } catch { return false } } export function toBigInt(value: bigint | BigIntProxy): bigint { if (value == null) return null if (value instanceof BigInt) return value else if (value["@serializedBigInt"] != null) return BigInt(value["@serializedBigInt"]) else return BigInt(0) } export type KITEM2 = { [K in keyof T]?: K extends KKey ? KITEM2 : never } & { ["@attr"]: KAttrMap2 ["@content"]: T extends string | Buffer | boolean | number[] | bigint[] ? T : T extends number | bigint ? [T] : T extends BufferArray ? Buffer : T extends NumberGroup ? TGroup : T extends BigIntProxy ? [bigint] : never } export type KAttrMap2 = { [key: string]: string } & { __type?: T extends TypeForKItem ? KTypeConvert : never __count?: T extends TypeForKArray ? number : never } export function ITEM2(ktype: KTypeConvert, value: T, attr?: KAttrMap2): KITEM2 { // let result // if (value instanceof NumberGroup && IsNumberGroupKType(ktype)) { // result = K.ITEM(>ktype, value.value, attr) // } else if (Array.isArray(value) && IsNumericKType(ktype)) { // result = K.ARRAY(>ktype, value, attr) // } else if (value instanceof BufferArray && IsNumericKType(ktype)) { // result = K.ARRAY(>ktype, value.value, attr) // } else if (typeof value != "object" && typeof value != "function") { // result = K.ITEM(ktype, value, attr) // } else { // Object.assign(result, value, { ["@attr"]: attr }) // result["@attr"].__type = ktype // } // return >result let result = >{} result["@attr"] = Object.assign({}, attr, (!isNumberGroupKType(ktype) && isNumericKType(ktype) && Array.isArray(value)) ? { __type: ktype, __count: (value).length } : { __type: ktype }) if ((ktype == "bool") && (typeof value == "boolean")) { result["@content"] = (value ? [1] : [0]) } else if ((ktype == "bin") && value instanceof Buffer) { result = K.ITEM("bin", value, result["@attr"]) } else if (((ktype == "s8") || (ktype == "u8")) && isBufferArray(value)) { result["@content"] = value["@bufferArrayValue"].toJSON() result["@attr"].__count = value["@bufferArrayValue"].byteLength } else if (isNumericKType(ktype) && !Array.isArray(value)) { result["@content"] = [value] } else if (isNumberGroupKType(ktype) && isNumberGroup(value)) { result["@content"] = value["@numberGroupValue"] } else if (isBigIntProxy(value)) { result["@content"] = BigInt(value["@serializedBigInt"]) } else { result["@content"] = value } if (isKIntType(ktype) && Array.isArray(result["@content"])) for (let i = 0; i < result["@content"].length; i++) (result["@content"])[i] = Math.trunc(result["@content"][i]) return result } export type KObjectMappingRecord = { [K in KKey]: T[K] extends TypeForKItem ? KObjectMappingElementInfer : KObjectMappingRecord } & KObjectMappingElementInfer export interface KObjectMappingElement { $type?: TKType, $targetKey?: string, $convert?: (source: T) => T $convertBack?: (target: T) => T $fallbackValue?: TKType extends "kignore" ? T : never $defaultValue?: T } type KObjectMappingElementInfer = KObjectMappingElement extends KType ? KTypeConvert : never) | never | "kignore"> export type KAttrRecord = { [K in keyof T]?: T extends TypeForKItem ? KAttrMap2 : KAttrRecord } & { selfAttr?: KAttrMap2 } export function getCollectionMappingElement>(collectionName: TCollection extends ICollection ? TName : never): KObjectMappingElement ? TName : unknown, "kignore"> { return ignoreme("collection", collectionName) } function isKType(type: TType): boolean { return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "time", "ip4", "float", "double", "bool", "s64", "u64", "2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "2f", "2d", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "3f", "3d", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "4f", "4d", "2b", "3b", "4b", "vb", "2s64", "2u64", "3s64", "3u64", "4s64", "4u64", "vs8", "vu8", "vs16", "vu16", "str", "bin"].includes(type) } function isKIntType(type: TType): boolean { return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "2b", "3b", "4b", "vb", "vs8", "vu8", "vs16", "vu16"].includes(type) } function isKBigIntType(type: TType): boolean { return (typeof (type) == "string") && ["s64", "u64"].includes(type) } function isNumericKType(type: TType): boolean { return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "time", "ip4", "float", "double", "bool", "s64", "u64"].includes(type) } function isNumberGroupKType(type: TType): boolean { return (typeof (type) == "string") && ["2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "2f", "2d", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "3f", "3d", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "4f", "4d", "2b", "3b", "4b", "vb", "2s64", "2u64", "3s64", "3u64", "4s64", "4u64", "vs8", "vu8", "vs16", "vu16"].includes(type) } function isNumericKey(k: any): boolean { return (typeof k == "number") || (parseInt(k).toString() == k) } function increaseNumericKey(k: T, step: number = 1): T { return (typeof k == "number") ? (k + step) : (typeof k == "string" && parseInt(k).toString() == k) ? (parseInt(k) + step) : k } function isEmptyKObject(o: object): boolean { return (Object.keys(o).length == 0) || ((Object.keys(o).length == 1) && (o["@attr"] != null)) } function isKMapRecordReservedKey(key: string): boolean { return ["$type", "$targetKey", "$convert", "$convertBack", "$fallbackValue", "$defaultValue"].includes(key) } function isKArray(data: KITEM2): boolean { return (data["@attr"] != null) && (data["@attr"].__count != null) } export function appendMappingElement(map: KObjectMappingRecord, element: KObjectMappingElementInfer): KObjectMappingRecord { let result = >{} Object.assign(result, map, element) return result } export function mapKObject(data: T, kMapRecord: KObjectMappingRecord, kAttrRecord: KAttrRecord = >{}): KITEM2 { if (data == null) return >{} let result: KITEM2 = (((0 in data) && data instanceof Object) ? [] : {}) if (kAttrRecord.selfAttr != null) result["@attr"] = kAttrRecord.selfAttr if (data instanceof Object) { for (let __k in data) { let k: keyof T = __k let mapK: keyof T = __k let attrK: keyof T = __k if (!(k in kMapRecord) && isNumericKey(k)) { for (let i = parseInt(k) - 1; i >= 0; i--) if (kMapRecord[i]) { mapK = i break } } if (!(k in kAttrRecord) && isNumericKey(k)) { for (let i = parseInt(k) - 1; i >= 0; i--) if (kAttrRecord[i]) { attrK = i break } } if (mapK in kMapRecord) { let target = [keyof T]>{} let targetMap = kMapRecord[>mapK] let targetKey: keyof T = (targetMap.$targetKey != null) ? targetMap.$targetKey : k let targetValue = (targetMap.$convert != null) ? >>targetMap.$convert(data[k]) : data[k] let targetAttr = kAttrRecord[attrK] if (targetMap.$type) { let tt = targetMap.$type if (tt == "kignore") continue target["@attr"] = Object.assign({}, targetAttr, (!isNumberGroupKType(tt) && isNumericKType(tt) && Array.isArray(data[k]) && Array.isArray(targetValue)) ? { __type: tt, __count: (targetValue).length } : { __type: tt }) if ((tt == "bool") && (typeof targetValue == "boolean")) { target["@content"] = (targetValue ? [1] : [0]) } else if ((tt == "bin") && targetValue instanceof Buffer) { target = K.ITEM("bin", targetValue, target["@attr"]) } else if (((tt == "s8") || (tt == "u8")) && isBufferArray(targetValue)) { target["@content"] = targetValue["@bufferArrayValue"] } else if (isNumericKType(tt) && !Array.isArray(targetValue)) { target["@content"] = [targetValue] } else if (isNumberGroupKType(tt) && isNumberGroup(targetValue)) { target["@content"] = targetValue["@numberGroupValue"] } else if (isBufferArray(targetValue)) { target["@content"] = targetValue["@bufferArrayValue"].toJSON() target["@attr"].__count = targetValue["@bufferArrayValue"].byteLength } else if (isBigIntProxy(targetValue)) { target["@content"] = BigInt(targetValue["@serializedBigInt"]) } else { target["@content"] = targetValue } if (isKIntType(tt) && Array.isArray(target["@content"])) for (let i = 0; i < target["@content"].length; i++) (target["@content"])[i] = Math.trunc(target["@content"][i]) } else { target = mapKObject(targetValue, >targetMap, >targetAttr) } result[targetKey] = target } } } else result = ITEM2(>kAttrRecord.selfAttr.$type, data, kAttrRecord.selfAttr) return result } export type MapBackResult = { data: T, attr?: KAttrRecord } export function mapBackKObject(data: KITEM2, kMapRecord?: KObjectMappingRecord): MapBackResult { if (kMapRecord == null) { if (data["@content"] || data["@attr"]) return { data: data["@content"], attr: data["@attr"] } else return { data: data } } let result: T = ((Array.isArray(data) || 0 in kMapRecord) ? [] : {}) let resultAttr: KAttrRecord = { selfAttr: data["@attr"] ? data["@attr"] : null } for (let __k in kMapRecord) { if (isKMapRecordReservedKey(__k)) continue let k = __k let preservK = __k do { let targetMap = kMapRecord[>preservK] let targetKey = (targetMap.$targetKey ? targetMap.$targetKey : k) let doOnceFlag = (isNumericKey(targetKey) && (data[targetKey] == null) && !isEmptyKObject(data)) let targetValue = [keyof T]>(doOnceFlag ? data : data[targetKey]) if (targetMap.$type == "kignore") { result[k] = targetMap.$fallbackValue if ((targetValue != null) && (targetValue["@attr"] != null)) resultAttr[k] = [keyof T]>{ selfAttr: targetValue["@attr"] } continue } if (targetValue == null) { if (targetMap.$convertBack != null) result[k] = targetMap.$convertBack(null) continue } if (targetValue["@attr"] != null) { let targetAttr: KAttrMap2 = targetValue["@attr"] let targetResult if (targetAttr.__type != null) { // KITEM targetResult = targetValue["@content"] if (isNumberGroupKType(targetAttr.__type)) { // KITEM2 // TODO: bigint number group targetResult = NumberGroup(targetResult) } else if (targetAttr.__type == "bin") { // KITEM<"bin"> targetResult = targetResult } else if ((targetAttr.__type == "s8" || targetAttr.__type == "u8") && (targetResult?.type == "Buffer") && Array.isArray(targetResult?.data)) { // KITEM2 targetResult = BufferArray(Buffer.from(targetResult.data)) } else if (targetAttr.__type == "bool") { // KITEM<"bool"> targetResult = targetResult[0] == 1 ? true : false } else if (Array.isArray(targetResult) && (targetAttr.__count == null) && isNumericKType(targetAttr.__type)) { // KITEM targetResult = ((targetAttr.__type == "s64") || (targetAttr.__type == "u64")) ? BigIntProxy(BigInt(targetResult[0])) : targetResult[0] } result[k] = (targetMap.$convertBack != null) ? targetMap.$convertBack(targetResult) : targetResult } else { // KObject targetResult = (targetMap.$convertBack != null) ? targetMap.$convertBack(targetValue) : targetValue; let partial = mapBackKObject(targetResult, targetMap) result[k] = partial.data resultAttr[k] = partial.attr } } else { // KObject let targetResult = (targetMap.$convertBack != null) ? targetMap.$convertBack(targetValue) : targetValue; let partial = mapBackKObject(targetResult, targetMap) result[k] = partial.data resultAttr[k] = partial.attr } k = increaseNumericKey(k) if (doOnceFlag || (isNumericKey(k) && (data[(targetMap.$targetKey ? targetMap.$targetKey : k)] == null))) break } while (isNumericKey(k) && !(k in kMapRecord)) } return { data: result, attr: resultAttr } } export function s8me(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement { return { $type: "s8", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function u8me(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement { return { $type: "u8", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function s16me(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement { return { $type: "s16", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function u16me(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement { return { $type: "u16", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function s32me(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement { return { $type: "s32", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function u32me(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement { return { $type: "u32", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function s64me(targetKey?: string, defaultValue?: bigint | BigIntProxy, convert?: (source: bigint | BigIntProxy) => bigint | BigIntProxy, convertBack?: (target: bigint | BigIntProxy) => bigint | BigIntProxy): KObjectMappingElement { return { $type: "s64", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function u64me(targetKey?: string, defaultValue?: bigint | BigIntProxy, convert?: (source: bigint | BigIntProxy) => bigint | BigIntProxy, convertBack?: (target: bigint | BigIntProxy) => bigint | BigIntProxy): KObjectMappingElement { return { $type: "u64", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function boolme(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement { return { $type: "bool", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function strme(targetKey?: string, defaultValue?: TName, convert?: (source: TName) => TName, convertBack?: (target: TName) => TName): KObjectMappingElement { return { $type: "str", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function binme(targetKey?: string, defaultValue?: Buffer, convert?: (source: Buffer) => Buffer, convertBack?: (target: Buffer) => Buffer): KObjectMappingElement { return { $type: "bin", $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export function ignoreme(targetKey?: string, fallbackValue?: T): KObjectMappingElement { return { $type: "kignore", $fallbackValue: fallbackValue } } export function me(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement { return { $targetKey: targetKey, $convert: convert, $convertBack: convertBack, $defaultValue: defaultValue } } export const colme = getCollectionMappingElement export const appendme = appendMappingElement export const mapK = mapKObject export const bacK = mapBackKObject export function fromMap(map: KObjectMappingRecord): T { let result = {} if (map.$type == "kignore") return map.$fallbackValue if (map.$defaultValue != null) return map.$defaultValue if (map.$type != null) { if (isNumericKType(map.$type)) { if (map.$type == "bool") return false else return 0 } else if (isKBigIntType(map.$type)) return BigInt(0) else if (isNumberGroupKType(map.$type)) return NumberGroup([0]) else if (map.$type == "str") return "" else return null } for (let k in map) { if (isKMapRecordReservedKey(k)) continue let value = fromMap(map[k]) if (value != null) result[k] = value } return result } export type KM = KObjectMappingRecord