Files
cfw-autumn/shared/utils/utils.ts

170 lines
4.7 KiB
TypeScript

import { Decimal } from "decimal.js";
export const nullish = <T>(
value: T | null | undefined,
): value is null | undefined => {
return value === null || value === undefined;
};
export const notNullish = <T>(value: T | null | undefined): value is T =>
value !== null && value !== undefined;
export const idRegex = /^[a-zA-Z0-9_-]+$/;
export const sumValues = (vals: number[]) => {
return vals.reduce((acc, curr) => acc.add(curr), new Decimal(0)).toNumber();
};
export const keyToTitle = (
key: string,
options?: { exclusionMap?: Record<string, string> },
) => {
if (options?.exclusionMap?.[key]) {
return options.exclusionMap[key];
}
return key
.replace(/[-_]/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase());
};
const sha256K = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
];
const rightRotate = (value: number, bits: number) =>
(value >>> bits) | (value << (32 - bits));
const bytesToBase64 = (bytes: Uint8Array): string => {
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
};
/** Cross-runtime SHA-256 hash. Keep this sync for Stripe idempotency key builders. */
export const hashString = (str: string): string => {
const bytes = new TextEncoder().encode(str);
const bitLength = bytes.length * 8;
const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
const padded = new Uint8Array(paddedLength);
padded.set(bytes);
padded[bytes.length] = 0x80;
const view = new DataView(padded.buffer);
view.setUint32(paddedLength - 4, bitLength, false);
let h0 = 0x6a09e667;
let h1 = 0xbb67ae85;
let h2 = 0x3c6ef372;
let h3 = 0xa54ff53a;
let h4 = 0x510e527f;
let h5 = 0x9b05688c;
let h6 = 0x1f83d9ab;
let h7 = 0x5be0cd19;
const w = new Uint32Array(64);
for (let offset = 0; offset < paddedLength; offset += 64) {
for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4, false);
for (let i = 16; i < 64; i++) {
const s0 =
rightRotate(w[i - 15], 7) ^
rightRotate(w[i - 15], 18) ^
(w[i - 15] >>> 3);
const s1 =
rightRotate(w[i - 2], 17) ^
rightRotate(w[i - 2], 19) ^
(w[i - 2] >>> 10);
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
}
let a = h0;
let b = h1;
let c = h2;
let d = h3;
let e = h4;
let f = h5;
let g = h6;
let h = h7;
for (let i = 0; i < 64; i++) {
const s1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25);
const ch = (e & f) ^ (~e & g);
const temp1 = (h + s1 + ch + sha256K[i] + w[i]) >>> 0;
const s0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22);
const maj = (a & b) ^ (a & c) ^ (b & c);
const temp2 = (s0 + maj) >>> 0;
h = g;
g = f;
f = e;
e = (d + temp1) >>> 0;
d = c;
c = b;
b = a;
a = (temp1 + temp2) >>> 0;
}
h0 = (h0 + a) >>> 0;
h1 = (h1 + b) >>> 0;
h2 = (h2 + c) >>> 0;
h3 = (h3 + d) >>> 0;
h4 = (h4 + e) >>> 0;
h5 = (h5 + f) >>> 0;
h6 = (h6 + g) >>> 0;
h7 = (h7 + h) >>> 0;
}
const digest = new Uint8Array(32);
const digestView = new DataView(digest.buffer);
[h0, h1, h2, h3, h4, h5, h6, h7].forEach((value, index) => {
digestView.setUint32(index * 4, value, false);
});
return bytesToBase64(digest);
};
// Types for the result object with discriminated union
type Success<T> = {
data: T;
error: null;
};
type Failure<E> = {
data: null;
error: E;
};
type Result<T, E = Error> = Success<T> | Failure<E>;
/** Wraps a promise and returns a discriminated union result */
export async function tryCatch<T, E = Error>(
promise: Promise<T>,
): Promise<Result<T, E>> {
try {
const data = await promise;
return { data, error: null };
} catch (error) {
return { data: null, error: error as E };
}
}
/** Sleep until a specific epoch timestamp (in milliseconds) */
export function sleepUntil(epochMs: number): Promise<void> {
const now = Date.now();
const delay = epochMs - now;
if (delay <= 0) return Promise.resolve();
return new Promise((resolve) => setTimeout(resolve, delay));
}
export const deduplicateArray = <T>(array: T[]): T[] => {
return Array.from(new Set(array));
};