170 lines
4.7 KiB
TypeScript
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));
|
|
};
|