addons/shared/utils.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {Json} from './typeUtils';
b69ab319
b69ab3110export function notEmpty<T>(value: T | null | undefined): value is T {
b69ab3111 return value !== null && value !== undefined;
b69ab3112}
b69ab3113
b69ab3114/**
b69ab3115 * Throw if value is `null` or `undefined`.
b69ab3116 */
b69ab3117export function nullthrows<T>(value: T | undefined | null): T {
b69ab3118 if (value == null) {
b69ab3119 throw new Error(`expected value not to be ${value}`);
b69ab3120 }
b69ab3121 return value;
b69ab3122}
b69ab3123
b69ab3124/**
b69ab3125 * generate a small random ID string via time in ms + random number encoded as a [0-9a-z]+ string
b69ab3126 * This should not be used for cryptographic purposes or if universal uniqueness is absolutely necessary
b69ab3127 */
b69ab3128export function randomId(): string {
b69ab3129 return Date.now().toString(36) + Math.random().toString(36);
b69ab3130}
b69ab3131
b69ab3132export type Deferred<T> = {
b69ab3133 promise: Promise<T>;
b69ab3134 resolve: (t: T) => void;
b69ab3135 reject: (e: Error) => void;
b69ab3136};
b69ab3137/**
b69ab3138 * Wraps `new Promise<T>()`, so you can access resolve/reject outside of the callback.
b69ab3139 * Useful for externally resolving promises in tests.
b69ab3140 */
b69ab3141export function defer<T>(): Deferred<T> {
b69ab3142 const deferred = {
b69ab3143 promise: undefined as unknown as Promise<T>,
b69ab3144 resolve: undefined as unknown as (t: T) => void,
b69ab3145 reject: undefined as unknown as (e: Error) => void,
b69ab3146 };
b69ab3147 deferred.promise = new Promise<T>((resolve: (t: T) => void, reject: (e: Error) => void) => {
b69ab3148 deferred.resolve = resolve;
b69ab3149 deferred.reject = reject;
b69ab3150 });
b69ab3151 return deferred;
b69ab3152}
b69ab3153
b69ab3154/**
b69ab3155 * Returns the part of the string after the last occurrence of delimiter,
b69ab3156 * or the entire string if no matches are found.
b69ab3157 * (default delimiter is '/')
b69ab3158 *
b69ab3159 * ```
b69ab3160 * basename('/path/to/foo.txt', '/') -> 'foo.txt'
b69ab3161 * basename('foo.txt', '/') -> 'foo.txt'
b69ab3162 * basename('/path/', '/') -> ''
b69ab3163 * ```
b69ab3164 */
b69ab3165export function basename(s: string, delimiter = '/') {
b69ab3166 const foundIndex = s.lastIndexOf(delimiter);
b69ab3167 if (foundIndex === -1) {
b69ab3168 return s;
b69ab3169 }
b69ab3170 return s.slice(foundIndex + 1);
b69ab3171}
b69ab3172
b69ab3173/**
b69ab3174 * Given a multi-line string, return the first line excluding '\n'.
b69ab3175 * If no newlines in the string, return the whole string.
b69ab3176 */
b69ab3177export function firstLine(s: string): string {
b69ab3178 return s.split('\n', 1)[0];
b69ab3179}
b69ab3180
b69ab3181/**
b69ab3182 * Applies a function to each key & value in an Object.
b69ab3183 * ```
b69ab3184 * mapObject(
b69ab3185 * {foo: 1, bar: 2},
b69ab3186 * ([key, value]) => ['_' + key, value + 1]
b69ab3187 * )
b69ab3188 * => {_foo: 2, _bar: 3}
b69ab3189 * ```
b69ab3190 */
b69ab3191export function mapObject<K1 extends string | number, V1, K2 extends string | number, V2>(
b69ab3192 o: Record<K1, V1>,
b69ab3193 func: (param: [K1, V1]) => [K2, V2],
b69ab3194): Record<K2, V2> {
b69ab3195 return Object.fromEntries((Object.entries(o) as Array<[K1, V1]>).map(func)) as Record<K2, V2>;
b69ab3196}
b69ab3197
b69ab3198/**
b69ab3199 * Test if a generator yields the given value.
b69ab31100 * `value` can be either a value to test equality, or a function to customize the equality test.
b69ab31101 */
b69ab31102export function generatorContains<V>(
b69ab31103 gen: IterableIterator<V>,
b69ab31104 value: V | ((v: V) => boolean),
b69ab31105): boolean {
b69ab31106 const test = typeof value === 'function' ? (value as (v: V) => boolean) : (v: V) => v === value;
b69ab31107 for (const v of gen) {
b69ab31108 if (test(v)) {
b69ab31109 return true;
b69ab31110 }
b69ab31111 }
b69ab31112 return false;
b69ab31113}
b69ab31114
b69ab31115/**
b69ab31116 * Zip 2 iterators.
b69ab31117 */
b69ab31118export function* zip<T, U>(iter1: Iterable<T>, iter2: Iterable<U>): IterableIterator<[T, U]> {
b69ab31119 const iterator1 = iter1[Symbol.iterator]();
b69ab31120 const iterator2 = iter2[Symbol.iterator]();
b69ab31121 while (true) {
b69ab31122 const result1 = iterator1.next();
b69ab31123 const result2 = iterator2.next();
b69ab31124 if (result1.done || result2.done) {
b69ab31125 break;
b69ab31126 }
b69ab31127 yield [result1.value, result2.value];
b69ab31128 }
b69ab31129}
b69ab31130
b69ab31131/** Truncate a long string. */
b69ab31132export function truncate(text: string, maxLength = 100): string {
b69ab31133 return text.length > maxLength ? text.substring(0, Math.max(0, maxLength - 1)) + '…' : text;
b69ab31134}
b69ab31135
b69ab31136export function isPromise<T>(o: unknown): o is Promise<T> {
b69ab31137 return typeof (o as {then?: () => void})?.then === 'function';
b69ab31138}
b69ab31139
b69ab31140export function tryJsonParse(s: string): Json | undefined {
b69ab31141 try {
b69ab31142 return JSON.parse(s);
b69ab31143 } catch {
b69ab31144 return undefined;
b69ab31145 }
b69ab31146}
b69ab31147
b69ab31148/**
b69ab31149 * Like Array.filter, but separates elements that pass from those that don't pass and return both arrays.
b69ab31150 * For example, partition([1, 2, 3], n => n % 2 === 0) returns [[2], [1, 3]]
b69ab31151 */
b69ab31152export function partition<T>(a: Array<T>, predicate: (item: T) => boolean): [Array<T>, Array<T>] {
b69ab31153 const [passed, failed] = [[], []] as [Array<T>, Array<T>];
b69ab31154 for (const item of a) {
b69ab31155 (predicate(item) ? passed : failed).push(item);
b69ab31156 }
b69ab31157 return [passed, failed];
b69ab31158}
b69ab31159
b69ab31160/**
b69ab31161 * Like Array.filter, but separates elements that pass from those that don't pass and return both arrays.
b69ab31162 * For example, partition([1, 2, 3], n => n % 2 === 0) returns [[2], [1, 3]]
b69ab31163 */
b69ab31164export function group<ArrayType, BucketType extends string | number>(
b69ab31165 a: ReadonlyArray<ArrayType>,
b69ab31166 bucket: (item: ArrayType) => BucketType,
b69ab31167): Record<BucketType, Array<ArrayType> | undefined> {
b69ab31168 const result = {} as Record<BucketType, Array<ArrayType>>;
b69ab31169 for (const item of a) {
b69ab31170 const b = bucket(item);
b69ab31171 const existing = result[b] ?? [];
b69ab31172 existing.push(item);
b69ab31173 result[b] = existing;
b69ab31174 }
b69ab31175 return result;
b69ab31176}
b69ab31177
b69ab31178/**
b69ab31179 * Split string `s` with the `sep` once.
b69ab31180 * If `s` does not contain `sep`, return undefined.
b69ab31181 */
b69ab31182export function splitOnce(s: string, sep: string): [string, string] | undefined {
b69ab31183 const index = s.indexOf(sep);
b69ab31184 if (index < 0) {
b69ab31185 return undefined;
b69ab31186 }
b69ab31187 return [s.substring(0, index), s.substring(index + sep.length)];
b69ab31188}
b69ab31189
b69ab31190/**
b69ab31191 * Like Array's .map() but for iterators.
b69ab31192 * Returns a new iterator applying a function to each value in the input.
b69ab31193 */
b69ab31194export function* mapIterable<T, R>(iterable: Iterable<T>, mapFn: (t: T) => R): IterableIterator<R> {
b69ab31195 for (const item of iterable) {
b69ab31196 yield mapFn(item);
b69ab31197 }
b69ab31198}
b69ab31199
b69ab31200export function base64Decode(data: string): ArrayBuffer {
b69ab31201 return Buffer.from(data, 'base64');
b69ab31202}
b69ab31203
b69ab31204/** Deduplicate items in an array. */
b69ab31205export function dedup<T>(arr: Array<T>): Array<T> {
b69ab31206 return Array.from(new Set(arr));
b69ab31207}