Skip to content

Instantly share code, notes, and snippets.

@navin-moorthy
Last active July 25, 2023 12:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save navin-moorthy/149a8c850cdca099a4e133e5e131c400 to your computer and use it in GitHub Desktop.
Save navin-moorthy/149a8c850cdca099a4e133e5e131c400 to your computer and use it in GitHub Desktop.
Common Utils
import { getErrorMessage } from "./getErrorMessage.js";
/**
* Adds additional error message to the existing error message.
* @param {unknown} error - The error object.
* @param {string} errorMessage - The additional error message to be added.
* @returns {string} - The updated error message.
*/
export function addAdditionalErrorMessage(
error: unknown,
errorMessage: string
): string {
return `${errorMessage} - ${getErrorMessage(error)}`;
}
/**
* Converts a number of seconds to a string in the format "Xh Ym Zs".
* @param {number} seconds - The number of seconds to convert.
* @returns {string} A string in the format "Xh Ym Zs".
*/
export function convertSecondsToTimeString(seconds: number): string {
const hours = Math.floor(seconds / 3_600);
const minutes = Math.floor((seconds % 3_600) / 60);
const remainingSeconds = seconds % 60;
return `${hours}h ${minutes}m ${remainingSeconds}s`;
}
/**
* Extracts the DD-MM-YYYY format from a given ISO date string.
*
* @param isoDate - The ISO date string to extract the date from.
* @returns The date in the format "DD-MM-YYYY".
*/
function extractIsoDate(isoDate: string): string {
const date = new Date(isoDate);
const formattedDate = `${date.getUTCDate()}-${
date.getUTCMonth() + 1
}-${date.getUTCFullYear()}`;
return formattedDate;
}
export const generateArrayRange = function* (
startArgument: number,
end: number,
step: number,
) {
let start = startArgument;
while (start < end) {
yield start;
start += step;
}
};
/**
* Represents an error object with a message.
*/
type ErrorWithMessage = {
message: string;
};
/**
* Determines whether a value is an Error object.
* @param {unknown} error - The value to test.
* @returns {boolean} True if the value is an Error object, false otherwise.
*/
export function isErrorObject(error: unknown): error is Error {
return error !== null && typeof error === "object";
}
/**
* Determines if an object is an ErrorWithMessage.
* @param {unknown} error - The object to check.
* @returns {boolean} True if the object is an ErrorWithMessage, false otherwise.
*/
function isErrorWithMessage(error: unknown): error is ErrorWithMessage {
return (
isErrorObject(error) &&
"message" in error &&
typeof (error as ErrorWithMessage).message === "string"
);
}
/**
* Converts an object to an ErrorWithMessage.
* @param {unknown} maybeError - The object to convert.
* @returns {ErrorWithMessage} An ErrorWithMessage object.
*/
function toErrorWithMessage(maybeError: unknown): ErrorWithMessage {
if (isErrorWithMessage(maybeError)) {
return maybeError;
}
try {
return new Error(JSON.stringify(maybeError));
} catch {
// fallback in case there's an error stringify the maybeError
// like with circular references for example.
return new Error(String(maybeError));
}
}
/**
* Gets the message property of an ErrorWithMessage object.
* @param {unknown} error - The ErrorWithMessage object.
* @returns {string} The message property of the ErrorWithMessage object.
*/
export function getErrorMessage(error: unknown): string {
return toErrorWithMessage(error).message;
}
// https://github.com/lodash/lodash/blob/master/.internal/getTag.js
import { isNullable } from "./isNullable.js";
// eslint-disable-next-line @typescript-eslint/unbound-method
const toString = Object.prototype.toString;
/**
* Gets the `toStringTag` of `value`.
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
export function getTag(value: unknown): string {
if (isNullable(value)) {
return value === undefined ? "[object Undefined]" : "[object Null]";
}
return toString.call(value);
}
export function handlePromiseAllSettledResults(
results: PromiseSettledResult<void>[],
) {
const isRejected = (
input: PromiseSettledResult<unknown>,
): input is PromiseRejectedResult => input.status === "rejected";
const isFulfilled = <T>(
input: PromiseSettledResult<T>,
): input is PromiseFulfilledResult<T> => input.status === "fulfilled";
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
const errors = results.filter(isRejected).map(result => result?.reason);
if (errors.length) {
// Aggregate all errors into one
throw new AggregateError(errors);
}
return results.filter(isFulfilled).map(result => result.value);
}
import { isNil } from "./isNil";
/**
* Determines whether a value is non-null and non-undefined.
* @template T - The type of the value to check.
* @param value - The value to check.
* @returns A boolean indicating whether the value is non-null and non-undefined.
*/
export function isDefined<T>(value: T | null | undefined): value is T {
return !isNil(value);
}
/**
* Checks if a value is a non-empty string.
* @param value - The value to check.
* @returns A boolean indicating whether the value is a non-empty string.
*/
export function isEmptyString(value: string): value is string {
return value === "";
}
/**
* Checks if a value is nullable (null or undefined).
* @template T - The type of the value being checked.
* @param {T} value - The value to check.
* @returns {value is null | undefined} - Returns true if the value is null or undefined, false otherwise.
* @example
* isNil(null); // returns true
* isNil(undefined); // returns true
* isNil('Hello'); // returns false
* isNil(42); // returns false
*/
export function isNil<T>(value: T): value is Extract<T, null | undefined> {
// eslint-disable-next-line no-eq-null, eqeqeq
return value == null;
}
// Cannot have isEmptyArray variant because of no NonEmptyArray type guard
/**
* Represents a non-empty array.
* @template T - The type of elements in the array.
*/
export type NonEmptyArray<T> = [T, ...T[]];
/**
* Checks if the provided array is non-empty.
* @template T - The type of elements in the array.
* @param {T[]} array - The array to check.
* @returns {boolean} - Returns true if the array is non-empty, false otherwise.
*/
export function isNonEmptyArray<T>(array: T[]): array is NonEmptyArray<T> {
return array.length > 0;
}
/* eslint-disable eqeqeq */
// https://github.com/lodash/lodash/blob/master/isSymbol.js
import { getTag } from "./getTag.js";
import { isNullable } from "./isNullable.js";
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
* @since 4.0.0
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* isSymbol(Symbol.iterator)
* // => true
*
* isSymbol('abc')
* // => false
*/
export function isSymbol(value: unknown): value is symbol {
const type = typeof value;
return (
type == "symbol" ||
(type === "object" &&
!isNullable(value) &&
getTag(value) == "[object Symbol]")
);
}
import { type NonEmptyArray } from "../../../utils/isNonEmptyArray";
export const joinArrayStrings = (
array: NonEmptyArray<string>,
separator = " · ",
) => array.filter((value) => value !== "").join(separator);
import { logger } from "./logger.js";
export function log(message: string) {
logger.info(message);
}
import { logger } from "./logger.js";
export function logError(message: string, error?: unknown) {
logger.error(`Message:: ${message} -`, error);
}
// Export is needed because TypeScript complains about an error otherwise:
// Error: …/tailwind-merge/src/config-utils.ts(8,17): semantic error TS4058: Return type of exported function has or is using name 'LruCache' from external module "…/tailwind-merge/src/lru-cache" but cannot be named.
export interface LruCache<Key, Value> {
get(key: Key): Value | undefined;
set(key: Key, value: Value): void;
}
// LRU cache inspired from hashlru (https://github.com/dominictarr/hashlru/blob/v1.0.4/index.js) but object replaced with Map to improve performance
export function getLruCache<Key, Value>(
maxCacheSize: number,
): LruCache<Key, Value> {
if (maxCacheSize < 1) {
return {
get: () => undefined,
set: () => {},
};
}
let cacheSize = 0;
let cache = new Map<Key, Value>();
let previousCache = new Map<Key, Value>();
function update(key: Key, value: Value) {
cache.set(key, value);
cacheSize++;
if (cacheSize > maxCacheSize) {
cacheSize = 0;
previousCache = cache;
cache = new Map();
}
}
return {
get(key) {
let value = cache.get(key);
if (value !== undefined) {
return value;
}
if ((value = previousCache.get(key)) !== undefined) {
update(key, value);
return value;
}
},
set(key, value) {
if (cache.has(key)) {
cache.set(key, value);
} else {
update(key, value);
}
},
};
}
export const cache = getLruCache<string, string>(100);
export const noop = (): void => {};
/**
* Returns an array of key-value pairs for a given object.
* @example
* const obj = { a: 1, b: 2, c: 3 };
* const result = entries(obj);
* // result: [['a', 1], ['b', 2], ['c', 3]]
* @example
* const obj = { name: 'John', age: 30, city: 'New York' };
* const result = entries(obj);
* // result: [['name', 'John'], ['age', 30], ['city', 'New York']]
* @param object - The object to get key-value pairs from.
* @returns An array of key-value pairs for the given object.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const entries = <T extends Record<string, any>>(
object: T,
): Array<[keyof T, T[keyof T]]> => Object.entries(object);
// Shorten a string to less than maxLen characters without truncating words.
export const shortenString = function (
string: string,
maxLength: number,
separator = " ",
) {
if (string.length <= maxLength) {
return string;
}
return string.slice(0, Math.max(0, string.lastIndexOf(separator, maxLength)));
};
import { addAdditionalErrorMessage } from "./addAdditionalErrorMessage";
/**
* Throws an error with an additional error message.
* @param error - The original error.
* @param errorMessage - The additional error message.
* @returns This function never returns as it always throws an error.
*/
export function throwError(error: unknown, errorMessage: string): never {
throw new Error(addAdditionalErrorMessage(error, errorMessage), {
cause: error,
});
}
/* eslint-disable eqeqeq */
// https://github.com/lodash/lodash/blob/master/toString.js
import { isNullable } from "./isNullable.js";
import { isSymbol } from "./isSymbol.js";
/**
* Used as references for various `Number` constants.
*/
const INFINITY = 1 / 0;
/**
* Converts `value` to a string. An empty string is returned for `null`
* and `undefined` values. The sign of `-0` is preserved.
* @since 4.0.0
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
* @example
*
* toString(null)
* // => ''
*
* toString(-0)
* // => '-0'
*
* toString([1, 2, 3])
* // => '1,2,3'
*/
export function toString(value: unknown): string {
if (isNullable(value)) {
return "";
}
// Exit early for strings to avoid a performance hit in some environments.
if (typeof value === "string") {
return value;
}
if (Array.isArray(value)) {
// Recursively convert values (susceptible to call stack limits).
return `${value.map((other) =>
isNullable(other) ? other : toString(other),
)}`;
}
if (isSymbol(value)) {
return value.toString();
}
const result = `${value}`;
// @ts-expect-error - TS doesn't like the value to be unknown compare to INFINITY
return result == "0" && 1 / value == -INFINITY ? "-0" : result;
}
// https://github.com/t3-oss/t3-env/blob/main/packages/core/index.ts
import { z, type ZodError, type ZodObject, type ZodType } from "zod";
import { type Prettify } from "./Prettify.js";
import { throwError } from "./throwError.js";
const onValidationError = (error: ZodError) => {
console.error(
"❌ Invalid environment variables:",
error.flatten().fieldErrors,
);
throw new Error("Invalid environment variables");
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Impossible<T extends Record<string, any>> = Partial<
Record<keyof T, never>
>;
type InferredSchema<Schema extends Record<string, ZodType>> = Partial<{
[Key in keyof Schema]: Schema[Key];
}>;
type ValidateEnvironmentVariablesOptions<
Schema extends Record<string, ZodType>,
> = Impossible<InferredSchema<never>> | InferredSchema<Schema>;
export function validateEnvironmentVariables<
Schema extends Record<string, ZodType> = NonNullable<unknown>,
>(
schemaObject: ValidateEnvironmentVariablesOptions<Schema>,
): Prettify<z.infer<ZodObject<Schema>>> {
try {
const _schemaObject =
typeof schemaObject === "object" ? (schemaObject as z.ZodRawShape) : {};
// eslint-disable-next-line node/no-process-env
const parsed = z.object(_schemaObject).safeParse(process.env);
if (!parsed.success) {
return onValidationError(parsed.error);
}
return parsed.data as unknown as Prettify<z.infer<ZodObject<Schema>>>;
} catch (error) {
return throwError(error, "Error in validateEnvironmentVariables function");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment