init; collecting some of my commonly used utils
This commit is contained in:
14
pkg/async.ts
Normal file
14
pkg/async.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export async function promiseAllSafe<T>(tasks: Promise<T>[]) {
|
||||
return await Promise.allSettled(tasks)
|
||||
.then(res => {
|
||||
const fulfilled: NonNullable<T>[] = [];
|
||||
|
||||
res.forEach(r => {
|
||||
if (r.status == 'fulfilled' && r.value) {
|
||||
fulfilled.push(r.value);
|
||||
}
|
||||
});
|
||||
|
||||
return fulfilled;
|
||||
}) satisfies Awaited<NonNullable<T>[]>;
|
||||
}
|
||||
28
pkg/csv.ts
Normal file
28
pkg/csv.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Options, parse, Parser } from "csv-parse";
|
||||
import { z } from "zod";
|
||||
|
||||
export async function readCSVToType<
|
||||
TData extends Record<string, unknown>
|
||||
>(
|
||||
text: string,
|
||||
validator: z.ZodType<TData>,
|
||||
options?: Options
|
||||
) {
|
||||
options ??= {
|
||||
columns: true,
|
||||
ignore_last_delimiters: true,
|
||||
skip_empty_lines: true,
|
||||
relax_column_count: true
|
||||
}
|
||||
|
||||
const parser = parse(text, options);
|
||||
const records: TData[] = [];
|
||||
|
||||
for await (const record of parser as (Parser & AsyncIterable<never>)) {
|
||||
records.push(
|
||||
validator.parse(record)
|
||||
)
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
21
pkg/dom.ts
Normal file
21
pkg/dom.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
export type PickElementProps<
|
||||
TElement extends keyof React.JSX.IntrinsicElements,
|
||||
TProps extends keyof React.ComponentProps<TElement>
|
||||
> = Pick<React.ComponentProps<TElement>, TProps>;
|
||||
|
||||
export function clickVirtualDownloadLink(
|
||||
url: string,
|
||||
filename: string,
|
||||
) {
|
||||
const a = document.createElement('a');
|
||||
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.target = "_blank";
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
53
pkg/obj.ts
Normal file
53
pkg/obj.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Given an object, return a copy of the object with the specified properties removed.
|
||||
*
|
||||
* @param obj - the object to strip properties from
|
||||
* @param keys - the properties to remove from the object
|
||||
* @returns - the new object with the specified properties removed
|
||||
*/
|
||||
export function excludeFromObject<
|
||||
TObj extends Record<string, unknown>
|
||||
>(
|
||||
obj: TObj,
|
||||
...keys: (keyof TObj)[]
|
||||
) {
|
||||
const copy = { ...obj }
|
||||
|
||||
for (const key of keys) {
|
||||
delete copy[key];
|
||||
}
|
||||
|
||||
return copy satisfies Omit<TObj, typeof keys[number]>;
|
||||
}
|
||||
|
||||
/** The functional opposite of `excludeFromObject` */
|
||||
export function pickFromObject<
|
||||
TObj extends Record<string, unknown>
|
||||
>(
|
||||
obj: TObj,
|
||||
...keys: (keyof TObj)[]
|
||||
) {
|
||||
const copy = {} as Pick<TObj, keyof TObj>;
|
||||
|
||||
for (const key of keys) {
|
||||
copy[key] = obj[key];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
export function hasAllKeys<
|
||||
TObj extends Record<string, unknown>
|
||||
>(obj: TObj, ...keys: (keyof TObj)[]) {
|
||||
for (const key of keys) {
|
||||
if (!obj[key]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default class Obj {
|
||||
static exclude = excludeFromObject;
|
||||
static pick = pickFromObject;
|
||||
static hasAllKeys = hasAllKeys;
|
||||
}
|
||||
40
pkg/queue.ts
Normal file
40
pkg/queue.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export default class Queue<TData> extends Array<TData> {
|
||||
constructor(...items: TData[]) {
|
||||
super(...items);
|
||||
}
|
||||
|
||||
public get isEmpty() {
|
||||
return this.length == 0;
|
||||
}
|
||||
|
||||
public get isNotEmpty() {
|
||||
return this.length > 0;
|
||||
}
|
||||
|
||||
public enqueue(item: TData) {
|
||||
this.push(item);
|
||||
return this;
|
||||
}
|
||||
|
||||
public dequeue() {
|
||||
return this.shift();
|
||||
}
|
||||
|
||||
public async* walk({ interval = 1000, chunkSize = 1 }) {
|
||||
while (this.isNotEmpty) {
|
||||
const item = this.getChunk(chunkSize);
|
||||
await new Promise(resolve => setTimeout(resolve, interval));
|
||||
yield item;
|
||||
}
|
||||
}
|
||||
|
||||
private getChunk(length: number) {
|
||||
const chunk = [];
|
||||
|
||||
while (chunk.length < length && this.isNotEmpty) {
|
||||
chunk.push(this.dequeue());
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
17
pkg/time.ts
Normal file
17
pkg/time.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export default class Time {
|
||||
static SECOND = 1000;
|
||||
static MINUTE = this.SECOND * 60;
|
||||
static HOUR = this.MINUTE * 60;
|
||||
static DAY = this.HOUR * 24;
|
||||
static WEEK = this.DAY * 7;
|
||||
static MONTH = this.DAY * 30;
|
||||
static YEAR = this.DAY * 365;
|
||||
|
||||
static seconds = (n: number) => n * this.SECOND;
|
||||
static minutes = (n: number) => n * this.MINUTE;
|
||||
static hours = (n: number) => n * this.HOUR;
|
||||
static days = (n: number) => n * this.DAY;
|
||||
static weeks = (n: number) => n * this.WEEK;
|
||||
static months = (n: number) => n * this.MONTH;
|
||||
static years = (n: number) => n * this.YEAR;
|
||||
}
|
||||
6
pkg/types.ts
Normal file
6
pkg/types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export type Callable<
|
||||
TReturn = void,
|
||||
TArgs extends unknown[] = unknown[]
|
||||
> = (...args: TArgs) => TReturn;
|
||||
|
||||
export type Maybe<T> = T | null | undefined;
|
||||
7
pkg/validators.ts
Normal file
7
pkg/validators.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function must<T = unknown>(
|
||||
evaluation: T,
|
||||
errorMessage = "Failed to fulfill requirements for function"
|
||||
): NonNullable<T> | never {
|
||||
if (!evaluation) throw new Error(errorMessage);
|
||||
return evaluation;
|
||||
}
|
||||
Reference in New Issue
Block a user