integratedhub/utils/types.ts
2026-02-15 04:03:48 +00:00

276 lines
7.1 KiB
TypeScript

// utils/types.ts
export type Emit = (message: string) => void;
type JSONValue<T> =
| T
| string
| number
| boolean
| null
| JSONObject
| JSONArray;
interface JSONObject {
[key: string]: JSONValue<
string | number | boolean | null | JSONObject | JSONArray
>;
}
interface JSONArray extends
Array<
JSONValue<string | number | boolean | null | JSONObject | JSONArray>
> {}
export function rollUpArray<T, R>(
array: T[],
initialValue: R,
rollUpFn: (accumulator: R, currentItem: T) => R,
): R {
return array.reduce(rollUpFn, initialValue);
}
export function jsonDiff<T>(
target: JSONValue<T>,
newChange: JSONValue<T>,
): JSONValue<T> | undefined {
// If source is null or undefined, return undefined (no difference)
if (newChange === null || newChange === undefined) return undefined;
// If types don't match or primitives are different, return source
// If both are objects, compare recursively
if (typeof target === "object" && typeof newChange === "object") {
if (Array.isArray(target) && Array.isArray(newChange)) {
// If arrays, compare by index
const diffArray: JSONArray = [];
let hasDiff: boolean = false;
// Compare existing elements
const maxAlikeIndex = Math.min(target.length, newChange.length);
for (let i = 0; i < maxAlikeIndex; i++) {
if (
typeof target !== typeof newChange ||
(typeof target !== "object" && target !== newChange)
) {
diffArray[i] = newChange[i];
if (newChange[i]) {
hasDiff = true;
}
} else {
const diff = jsonDiff(target[i], newChange[i]);
if (diff !== undefined) {
diffArray[i] = diff;
if (diff !== null && diff !== undefined) {
hasDiff = true;
}
} else {
diffArray[i] = null;
}
}
}
for (let i = maxAlikeIndex; i < newChange.length; i++) {
//console.log("added to array ", JSON.stringify(newChange[i]));
diffArray[i] = newChange[i];
}
// Add additional elements from source
// for (let i = target.length; i < newChange.length; i++) {
// diffArray[i] = newChange[i];
// }
return hasDiff || target.length !== newChange.length ? diffArray : null;
} else if (!Array.isArray(target) && !Array.isArray(newChange)) {
// If objects, compare by key
const result: JSONObject = {};
let hasDiff = false;
// console.log("key",newChange);
for (const key in newChange) {
if (Object.prototype.hasOwnProperty.call(newChange, key)) {
const diff = jsonDiff(
(target as JSONObject)[key],
(newChange as JSONObject)[key],
);
if (diff !== undefined) {
result[key] = diff;
hasDiff = true;
}
}
}
return hasDiff ? result : undefined;
}
} else if (typeof target !== typeof newChange || target !== newChange) {
return newChange;
}
// For identical primitives or matching types, return undefined (no difference)
return undefined;
}
export function jsonMerge<T>(
target: JSONValue<T>,
newChange: JSONValue<T>,
): JSONValue<T> {
// If source is null or undefined, return target
if (newChange === null || newChange === undefined) return target;
// If types don't match, overwrite with source
if (typeof target !== typeof newChange) return newChange;
// If both are objects, merge recursively
if (typeof target === "object" && typeof newChange === "object") {
if (Array.isArray(target) && Array.isArray(newChange)) {
// If arrays, merge by index
const mergedArray = target.map((item, index) =>
index < newChange.length ? jsonMerge(item, newChange[index]) : item
);
// Add additional non-null elements from source
for (let i = target.length; i < newChange.length; i++) {
if (newChange[i] !== null && newChange[i] !== undefined) {
mergedArray.push(newChange[i]);
}
}
for (let i = newChange.length; i < target.length; i++) {
mergedArray.pop();
}
return mergedArray as JSONArray;
} else if (!Array.isArray(target) && !Array.isArray(newChange)) {
// If objects, merge by key
const result = { ...target as JSONObject };
for (const key in newChange) {
if (Object.prototype.hasOwnProperty.call(newChange, key)) {
result[key] = jsonMerge(
(target as JSONObject)[key],
(newChange as JSONObject)[key],
);
}
}
return result;
}
}
// For primitive types or mismatched types, return source
return newChange;
}
export function cloneObjectExcept<
T extends object,
K extends Extract<keyof T, string> = Extract<keyof T, string>,
>(
obj: T,
fieldToChange?: K[],
changeObjects?: Array<T[K]>,
): T {
if (Array.isArray(obj)) {
const result = [];
for (let i = 0; i < obj.length; ++i) {
const item = obj[i];
if (item && typeof item === "object") {
result.push(cloneObjectExcept(item));
} else {
result.push(item);
}
}
return result as T;
} else {
const result = {} as T;
for (const key in obj) {
if (
fieldToChange === undefined ||
!fieldToChange.some((changeKey) => changeKey === key)
) {
const objValue = obj[key];
if (
Object.prototype.hasOwnProperty.call(obj, key) &&
typeof objValue === "object"
) {
result[key] = cloneObjectExcept(
objValue as T[Extract<keyof T, string>] & object,
);
} else {
result[key] = objValue;
}
}
}
if (fieldToChange && changeObjects) {
for (let i = 0; i < fieldToChange.length; ++i) {
result[fieldToChange[i]] = changeObjects[i] as T[K];
}
}
return result;
}
}
export interface KvObj<T> {
key: string[];
value: T | null | undefined;
ok: boolean;
versionstamp: string;
}
export interface BlobObject {
obj_key: string;
any_value: string;
}
export interface SessionConfig {
device_id: string;
user_id: string;
other_data: string;
active_date: number;
quiz_configs: Array<Array<string>>;
active_route: string;
page_data: string;
active_quiz_index: number;
active_question_index: number;
}
export interface QuizConfig {
quiz_config_id: string;
quiz_name: string;
owner_id: string;
quiz_mode: string;
active_date: number;
tab_name?: string;
tab_index?: number;
list_index?: number;
questions: Array<{
id: string;
question_index?: number;
quiz_id: string;
owner_id: string;
language_1: string;
language_2: string;
text_1: string;
audio_file_1: string;
text_2: string;
audio_file_2: string;
order_num: number;
background_file: string;
background_image_data: string;
video_file: string;
video_file_data: string;
}>;
results: Array<{
success: boolean;
total_time: number;
help_count: number;
}>;
}
export interface AudioFile {
text: string;
audio_file: string;
}
export interface ImageFile {
image_text: string;
image_file: string;
image_data: string;
}