documentation, some basic testing
This commit is contained in:
1
.npmignore
Normal file
1
.npmignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tests
|
||||||
69
README.md
Normal file
69
README.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Utility Closet
|
||||||
|
|
||||||
|
I've collected a few personal favorite problem-solvers that I reuse often. They tend to solve common problems that I
|
||||||
|
encounter across domains, and often gravitate towards object- and type-wrangling.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install utility-closet
|
||||||
|
pnpm install utility-closet
|
||||||
|
bun install utility-closet
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Quick and easy object manipulation:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { excludeFromObject, pickFromObject } from "utility-closet/obj";
|
||||||
|
|
||||||
|
const starting = {
|
||||||
|
one: "one",
|
||||||
|
two: "two"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = excludeFromObject(starting, 'one') satisfies Exclude<typeof starting, 'one'>;
|
||||||
|
const otherResult = pickFromObject(starting, 'one') satisfies Pick<typeof starting, 'one'>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Moving fast in React:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import type { PickElementProps, ElementProps } from "utility-closet/dom"
|
||||||
|
|
||||||
|
type ButtonProps = PickElementProps<'button', 'onClick' | 'className'>;
|
||||||
|
|
||||||
|
function Button({ onClick, className }: ButtonProps) {
|
||||||
|
return (
|
||||||
|
<button onClick={onClick} className={className}>
|
||||||
|
Cool button
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// or if you want to skip some more steps:
|
||||||
|
type LinkProps = PickElementProps<'a', 'className' | 'href' | 'target' | 'rel'>;
|
||||||
|
function Link(props: LinkProps) {
|
||||||
|
return <a {...props}>Link</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
// this also pairs well with your own generics
|
||||||
|
type SVGProps<
|
||||||
|
TProps extends ElementProps<'svg'> = ElementProps<'svg'>
|
||||||
|
> = PickElementProps<'svg', TProps>;
|
||||||
|
|
||||||
|
function FirstSVG(props: SVGProps<'onClick'>) {
|
||||||
|
return <svg {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SecondSVG(props: SVGProps<'className'>) {
|
||||||
|
return <svg {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
// assigning a default value to the type parameter allows us to optionally reference an element's full subset of props
|
||||||
|
function UnboundedSVG(props: SVGProps) {
|
||||||
|
return <svg {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export async function promiseAllSafe<T>(tasks: Promise<T>[]) {
|
export async function promiseAllOptimistic<T>(tasks: Promise<T>[]) {
|
||||||
return await Promise.allSettled(tasks)
|
return await Promise.allSettled(tasks)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
const fulfilled: NonNullable<T>[] = [];
|
const fulfilled: NonNullable<T>[] = [];
|
||||||
|
|||||||
18
pkg/csv.ts
18
pkg/csv.ts
@@ -1,6 +1,15 @@
|
|||||||
import { Options, parse, Parser } from "csv-parse";
|
import { Options, parse, Parser } from "csv-parse";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw CSV data into a validated array of entries of a given type,
|
||||||
|
* specified by a Zod validator provided in the function parameters.
|
||||||
|
*
|
||||||
|
* @param text a raw CSV text entry containing the data you wish to read
|
||||||
|
* @param validator a Zod schema representing the type of the target object
|
||||||
|
* @param options optional configuration options for the CSV parser
|
||||||
|
* @returns an array of validated
|
||||||
|
*/
|
||||||
export async function readCSVToType<
|
export async function readCSVToType<
|
||||||
TData extends Record<string, unknown>
|
TData extends Record<string, unknown>
|
||||||
>(
|
>(
|
||||||
@@ -18,10 +27,13 @@ export async function readCSVToType<
|
|||||||
const parser = parse(text, options);
|
const parser = parse(text, options);
|
||||||
const records: TData[] = [];
|
const records: TData[] = [];
|
||||||
|
|
||||||
|
// the type of the iterable is irrelevant, as it will be asserted by the Zod schema
|
||||||
for await (const record of parser as (Parser & AsyncIterable<never>)) {
|
for await (const record of parser as (Parser & AsyncIterable<never>)) {
|
||||||
records.push(
|
try {
|
||||||
validator.parse(record)
|
records.push(validator.parse(record))
|
||||||
)
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
|
|||||||
22
pkg/dom.ts
22
pkg/dom.ts
@@ -1,10 +1,32 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an JSX element type `TElement`, returns a subset of its props
|
||||||
|
* specified in the union `TProps`.
|
||||||
|
*/
|
||||||
export type PickElementProps<
|
export type PickElementProps<
|
||||||
TElement extends keyof React.JSX.IntrinsicElements,
|
TElement extends keyof React.JSX.IntrinsicElements,
|
||||||
TProps extends keyof React.ComponentProps<TElement>
|
TProps extends keyof React.ComponentProps<TElement>
|
||||||
> = Pick<React.ComponentProps<TElement>, TProps>;
|
> = Pick<React.ComponentProps<TElement>, TProps>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an JSX element type `TElement`, returns its full set of props,
|
||||||
|
* excluding members of the union `TProps`.
|
||||||
|
*/
|
||||||
|
export type ExcludeElementProps<
|
||||||
|
TElement extends keyof React.JSX.IntrinsicElements,
|
||||||
|
TProps extends keyof React.ComponentProps<TElement>
|
||||||
|
> = Exclude<keyof React.ComponentProps<TElement>, TProps>;
|
||||||
|
|
||||||
|
export type ElementProps<TElement extends keyof React.JSX.IntrinsicElements> = React.ComponentProps<TElement>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mounts a virtual <a> tag and virtually clicks it, intiating a download
|
||||||
|
* on the client's device.
|
||||||
|
*
|
||||||
|
* @param url the URL location of the desired resource
|
||||||
|
* @param filename the name to assign to the download once completed
|
||||||
|
*/
|
||||||
export function clickVirtualDownloadLink(
|
export function clickVirtualDownloadLink(
|
||||||
url: string,
|
url: string,
|
||||||
filename: string,
|
filename: string,
|
||||||
|
|||||||
0
pkg/logger.ts
Normal file
0
pkg/logger.ts
Normal file
@@ -1,7 +1,14 @@
|
|||||||
export function must<T = unknown>(
|
/**
|
||||||
evaluation: T,
|
* Assert that a given value, @param evaluation, is truthy. @returns the evaluation, asserted as non-nullable.
|
||||||
errorMessage = "Failed to fulfill requirements for function"
|
*/
|
||||||
): NonNullable<T> | never {
|
|
||||||
if (!evaluation) throw new Error(errorMessage);
|
import { Callable } from "./types";
|
||||||
|
|
||||||
|
export function must<T = unknown>(evaluation: T, callback?: Callable<never>): NonNullable<T> | never {
|
||||||
|
if (!evaluation) {
|
||||||
|
if (!callback) throw new Error("Assertion failed: value is falsy");
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
return evaluation;
|
return evaluation;
|
||||||
}
|
}
|
||||||
|
|||||||
3
tests/csv.test.ts
Normal file
3
tests/csv.test.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { describe, assert, it } from "vitest";
|
||||||
|
|
||||||
|
|
||||||
62
tests/obj.test.ts
Normal file
62
tests/obj.test.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { describe, it, assert } from 'vitest';
|
||||||
|
import Obj from '../pkg/obj';
|
||||||
|
|
||||||
|
describe("obj", () => {
|
||||||
|
describe("exclude", () => {
|
||||||
|
it("should exclude properties from an object", () => {
|
||||||
|
const obj = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
c: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = Obj.exclude(obj, 'a', 'c');
|
||||||
|
|
||||||
|
assert(result.a === undefined);
|
||||||
|
assert(result.b === 2);
|
||||||
|
assert(result.c === undefined);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('pick', () => {
|
||||||
|
it('should pick properties from an object', () => {
|
||||||
|
const obj = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
c: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = Obj.pick(obj, 'a', 'c');
|
||||||
|
|
||||||
|
assert(result.a === 1);
|
||||||
|
assert(result.b === undefined);
|
||||||
|
assert(result.c === 3);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('hasAllKeys', () => {
|
||||||
|
it('should return true if all keys are present', () => {
|
||||||
|
const obj = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
c: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = Obj.hasAllKeys(obj, 'a', 'b', 'c');
|
||||||
|
|
||||||
|
assert(result === true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if any key is missing', () => {
|
||||||
|
const obj = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
const result = Obj.hasAllKeys(obj, 'a', 'b', 'c');
|
||||||
|
|
||||||
|
assert(result === false);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
Reference in New Issue
Block a user