2023-01-27 05:56:56 -08:00
|
|
|
import type { BinaryFileType } from './Interfaces';
|
2022-12-11 05:10:54 -08:00
|
|
|
|
2022-11-04 09:34:47 -07:00
|
|
|
export type Primitives = string | number | boolean | bigint | symbol | null | undefined;
|
|
|
|
|
2022-10-18 04:33:31 -07:00
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */
|
2022-11-02 09:44:12 -07:00
|
|
|
export const deepCopy = <T extends ((object | Date) & { toJSON?: () => string }) | Primitives>(
|
|
|
|
source: T,
|
|
|
|
hash = new WeakMap(),
|
|
|
|
path = '',
|
|
|
|
): T => {
|
2022-10-18 04:33:31 -07:00
|
|
|
const hasOwnProp = Object.prototype.hasOwnProperty.bind(source);
|
2022-10-28 06:25:44 -07:00
|
|
|
// Primitives & Null & Function
|
2022-11-02 09:44:12 -07:00
|
|
|
if (typeof source !== 'object' || source === null || typeof source === 'function') {
|
2022-10-18 04:33:31 -07:00
|
|
|
return source;
|
|
|
|
}
|
2022-11-02 09:44:12 -07:00
|
|
|
// Date and other objects with toJSON method
|
|
|
|
// TODO: remove this when other code parts not expecting objects with `.toJSON` method called and add back checking for Date and cloning it properly
|
|
|
|
if (typeof source.toJSON === 'function') {
|
|
|
|
return source.toJSON() as T;
|
|
|
|
}
|
2022-10-28 06:25:44 -07:00
|
|
|
if (hash.has(source)) {
|
|
|
|
return hash.get(source);
|
|
|
|
}
|
2022-10-18 04:33:31 -07:00
|
|
|
// Array
|
|
|
|
if (Array.isArray(source)) {
|
2022-11-02 09:44:12 -07:00
|
|
|
const clone = [];
|
2022-10-18 04:33:31 -07:00
|
|
|
const len = source.length;
|
2022-11-02 09:44:12 -07:00
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
clone[i] = deepCopy(source[i], hash, path + `[${i}]`);
|
2022-10-18 04:33:31 -07:00
|
|
|
}
|
2022-11-02 09:44:12 -07:00
|
|
|
return clone as T;
|
2022-10-18 04:33:31 -07:00
|
|
|
}
|
|
|
|
// Object
|
2022-11-02 09:44:12 -07:00
|
|
|
const clone = Object.create(Object.getPrototypeOf({}));
|
2022-10-28 06:25:44 -07:00
|
|
|
hash.set(source, clone);
|
2022-11-02 09:44:12 -07:00
|
|
|
for (const i in source) {
|
2022-10-18 04:33:31 -07:00
|
|
|
if (hasOwnProp(i)) {
|
2022-11-02 09:44:12 -07:00
|
|
|
clone[i] = deepCopy((source as any)[i], hash, path + `.${i}`);
|
2022-10-18 04:33:31 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return clone;
|
|
|
|
};
|
|
|
|
// eslint-enable
|
2022-10-21 11:52:43 -07:00
|
|
|
|
2022-10-24 03:48:16 -07:00
|
|
|
type MutuallyExclusive<T, U> =
|
|
|
|
| (T & { [k in Exclude<keyof U, keyof T>]?: never })
|
|
|
|
| (U & { [k in Exclude<keyof T, keyof U>]?: never });
|
|
|
|
|
|
|
|
type JSONParseOptions<T> = MutuallyExclusive<{ errorMessage: string }, { fallbackValue: T }>;
|
|
|
|
|
|
|
|
export const jsonParse = <T>(jsonString: string, options?: JSONParseOptions<T>): T => {
|
2022-10-21 11:52:43 -07:00
|
|
|
try {
|
|
|
|
return JSON.parse(jsonString) as T;
|
|
|
|
} catch (error) {
|
2022-10-24 03:48:16 -07:00
|
|
|
if (options?.fallbackValue !== undefined) {
|
2022-10-21 11:52:43 -07:00
|
|
|
return options.fallbackValue;
|
2022-10-24 03:48:16 -07:00
|
|
|
} else if (options?.errorMessage) {
|
2022-10-21 11:52:43 -07:00
|
|
|
throw new Error(options.errorMessage);
|
|
|
|
}
|
2022-10-24 03:48:16 -07:00
|
|
|
|
2022-10-21 11:52:43 -07:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
};
|
2022-11-08 08:06:00 -08:00
|
|
|
|
2023-03-21 07:34:30 -07:00
|
|
|
type JSONStringifyOptions = {
|
|
|
|
replaceCircularRefs?: boolean;
|
|
|
|
circularRefReplacement?: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
const getReplaceCircularReferencesFn = (options: JSONStringifyOptions) => {
|
|
|
|
const knownObjects = new WeakSet();
|
|
|
|
return (key: any, value: any) => {
|
|
|
|
if (typeof value === 'object' && value !== null) {
|
|
|
|
if (knownObjects.has(value)) {
|
|
|
|
return options?.circularRefReplacement ?? '[Circular Reference]';
|
|
|
|
}
|
|
|
|
knownObjects.add(value);
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export const jsonStringify = (obj: unknown, options: JSONStringifyOptions = {}): string => {
|
|
|
|
const replacer = options?.replaceCircularRefs
|
|
|
|
? getReplaceCircularReferencesFn(options)
|
|
|
|
: undefined;
|
|
|
|
return JSON.stringify(obj, replacer);
|
|
|
|
};
|
|
|
|
|
2022-11-08 08:06:00 -08:00
|
|
|
export const sleep = async (ms: number): Promise<void> =>
|
|
|
|
new Promise((resolve) => {
|
|
|
|
setTimeout(resolve, ms);
|
|
|
|
});
|
2022-12-11 05:10:54 -08:00
|
|
|
|
|
|
|
export function fileTypeFromMimeType(mimeType: string): BinaryFileType | undefined {
|
|
|
|
if (mimeType.startsWith('application/json')) return 'json';
|
|
|
|
if (mimeType.startsWith('image/')) return 'image';
|
|
|
|
if (mimeType.startsWith('video/')) return 'video';
|
|
|
|
if (mimeType.startsWith('text/')) return 'text';
|
|
|
|
return;
|
|
|
|
}
|
2022-12-22 01:27:14 -08:00
|
|
|
|
|
|
|
export function assert<T>(condition: T, msg?: string): asserts condition {
|
|
|
|
if (!condition) {
|
|
|
|
const error = new Error(msg ?? 'Invalid assertion');
|
|
|
|
// hide assert stack frame if supported
|
|
|
|
if (Error.hasOwnProperty('captureStackTrace')) {
|
|
|
|
// V8 only - https://nodejs.org/api/errors.html#errors_error_capturestacktrace_targetobject_constructoropt
|
|
|
|
Error.captureStackTrace(error, assert);
|
|
|
|
} else if (error.stack) {
|
|
|
|
// fallback for IE and Firefox
|
|
|
|
error.stack = error.stack
|
|
|
|
.split('\n')
|
|
|
|
.slice(1) // skip assert function from stack frames
|
|
|
|
.join('\n');
|
|
|
|
}
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|