diff --git a/CHANGELOG.md b/CHANGELOG.md index 0af6043..530f8b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # deverything +## 0.43.0 + +### Minor Changes + +- add 3rd party helpers (no deps) + ## 0.42.2 ### Patch Changes diff --git a/package.json b/package.json index 4f4c523..6ccfa4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "deverything", - "version": "0.42.2", + "version": "0.43.0", "description": "Everything you need for Dev", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/src/helpers/cyclicalItem.test.ts b/src/helpers/cyclicalItem.test.ts new file mode 100644 index 0000000..c4d77d2 --- /dev/null +++ b/src/helpers/cyclicalItem.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, test } from "@jest/globals"; +import { cyclicalItem } from "./cyclicalItem"; + +describe("cyclicalItem", () => { + test("works", async () => { + expect(cyclicalItem([], 0)).toBe(undefined); + expect(cyclicalItem([], 1)).toBe(undefined); + }); + + test("works", async () => { + expect(cyclicalItem([1], 0)).toBe(1); + expect(cyclicalItem([1], 1)).toBe(1); + }); + + test("works", async () => { + expect(cyclicalItem([1, 2, 3], 0)).toBe(1); + expect(cyclicalItem([1, 2, 3], 2)).toBe(3); + expect(cyclicalItem([1, 2, 3], 3)).toBe(1); + expect(cyclicalItem([1, 2, 3], 30)).toBe(1); + }); +}); diff --git a/src/helpers/cyclicalItem.ts b/src/helpers/cyclicalItem.ts new file mode 100644 index 0000000..3025d6f --- /dev/null +++ b/src/helpers/cyclicalItem.ts @@ -0,0 +1,6 @@ +/** + * @returns element from array at index, if index is greater than array length, it will loop back to the start of the array + */ +export const cyclicalItem = (array: T[], index: number): T => { + return array[index % array.length]; +}; diff --git a/src/helpers/firstKey.ts b/src/helpers/firstKey.ts index fece476..7c97a5d 100644 --- a/src/helpers/firstKey.ts +++ b/src/helpers/firstKey.ts @@ -1,4 +1,5 @@ -import { PlainObject } from "../types"; +import { ObjectKey, PlainObject } from "../types"; import { getKeys } from "./getKeys"; -export const firstKey = (arg: PlainObject): string => getKeys(arg)[0]; +export const firstKey = (arg: T): ObjectKey => + getKeys(arg)[0]; diff --git a/src/helpers/firstValue.ts b/src/helpers/firstValue.ts index ffb673e..65cf650 100644 --- a/src/helpers/firstValue.ts +++ b/src/helpers/firstValue.ts @@ -1,3 +1,4 @@ -import { PlainObject } from "../types"; +import { ObjectValue, PlainObject } from "../types"; -export const firstValue = (arg: PlainObject): any => Object.values(arg)[0]; +export const firstValue = (arg: T): ObjectValue => + Object.values(arg)[0]; diff --git a/src/helpers/getKeys.test.ts b/src/helpers/getKeys.test.ts index 3f62e56..ba9e081 100644 --- a/src/helpers/getKeys.test.ts +++ b/src/helpers/getKeys.test.ts @@ -2,16 +2,12 @@ import { describe, expect, test } from "@jest/globals"; import { getKeys } from "./getKeys"; describe("getKeys", () => { - test("{}", async () => { + test("constructors", async () => { expect(getKeys(new Date())).toStrictEqual([]); - expect(getKeys(Date)).toStrictEqual([]); - expect(getKeys(Function)).toStrictEqual([]); - expect(getKeys(new Function())).toStrictEqual([]); expect(getKeys(new Object())).toStrictEqual([]); - expect(getKeys(new Array())).toStrictEqual([]); }); - test("keys", async () => { + test("objects", async () => { expect(getKeys({ a: 1, b: 2 })).toStrictEqual(["a", "b"]); expect(getKeys({ [Symbol.for("1")]: 1, b: 2 })).toStrictEqual([ "b", diff --git a/src/helpers/getKeys.ts b/src/helpers/getKeys.ts index 9e43566..4965414 100644 --- a/src/helpers/getKeys.ts +++ b/src/helpers/getKeys.ts @@ -1,12 +1,14 @@ -export const getKeys = (arg: object) => { - return Object.keys(arg).concat(getEnumerableOwnPropertySymbols(arg)); +import { ObjectKeys, PlainObject } from "../types"; + +export const getKeys = (obj: T): ObjectKeys => { + return Object.keys(obj).concat(getEnumerableOwnPropertySymbols(obj)); }; // Object.keys does not return enumerable symbols -export const getEnumerableOwnPropertySymbols = (arg: object): any[] => { +export const getEnumerableOwnPropertySymbols = (obj: object): any[] => { return Object.getOwnPropertySymbols - ? Object.getOwnPropertySymbols(arg).filter(function (symbol) { - return Object.propertyIsEnumerable.call(arg, symbol); + ? Object.getOwnPropertySymbols(obj).filter(function (symbol) { + return Object.propertyIsEnumerable.call(obj, symbol); }) : []; }; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 9efcc06..7b9ca13 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -4,6 +4,7 @@ export * from "./arrayIntersection"; export * from "./capitalize"; export * from "./clamp"; export * from "./cleanSpaces"; +export * from "./cyclicalItem"; export * from "./dir"; export * from "./enumKeys"; export * from "./enumValues"; @@ -18,6 +19,7 @@ export * from "./keysLength"; export * from "./last"; export * from "./lastIndex"; export * from "./merge"; +export * from "./mergeArrays"; export * from "./moveToFirst"; export * from "./moveToLast"; export * from "./normalizeNumber"; diff --git a/src/helpers/mergeArrays.test.ts b/src/helpers/mergeArrays.test.ts new file mode 100644 index 0000000..bc1d489 --- /dev/null +++ b/src/helpers/mergeArrays.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, test } from "@jest/globals"; +import { mergeArrays } from "./mergeArrays"; + +describe("mergeArrays", () => { + test("array", async () => { + expect(mergeArrays([], [])).toStrictEqual([]); + expect(mergeArrays([1, 2, 3], [1])).toStrictEqual([1, 2, 3]); + expect(mergeArrays([1, 2, 3, 3], [1, 3])).toStrictEqual([1, 2, 3, 3]); + expect(mergeArrays([1, 2, 3, 3], [1, 3, 4, 4])).toStrictEqual([ + 1, 2, 3, 3, 4, 4, + ]); + }); +}); diff --git a/src/helpers/mergeArrays.ts b/src/helpers/mergeArrays.ts new file mode 100644 index 0000000..275cbe4 --- /dev/null +++ b/src/helpers/mergeArrays.ts @@ -0,0 +1,11 @@ +/** + * @description Merge two arrays, unique values, no options + * @example mergeArrays([1,2,3], [2,3,4]) => [1,2,3,4] + */ +export const mergeArrays = (arrayA: any[], arrayB: any[]) => { + return arrayA.concat( + arrayB.filter((item) => { + return !arrayA.includes(item); // TODO: use isSame for objects + }) + ); +}; diff --git a/src/index.ts b/src/index.ts index a79f0c5..742819e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,9 @@ export * from "./checks"; +export * from "./formatters"; export * from "./helpers"; export * from "./math"; +export * from "./prisma"; export * from "./random"; +export * from "./trpc"; export * from "./types"; export * from "./validators"; -export * from "./formatters"; diff --git a/src/math/index.ts b/src/math/index.ts index c56f936..3ce5728 100644 --- a/src/math/index.ts +++ b/src/math/index.ts @@ -2,4 +2,5 @@ export * from "./average"; export * from "./max"; export * from "./min"; export * from "./multiply"; +export * from "./percentageChange"; export * from "./sum"; diff --git a/src/math/percentageChange.test.ts b/src/math/percentageChange.test.ts new file mode 100644 index 0000000..8771ef5 --- /dev/null +++ b/src/math/percentageChange.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, test } from "@jest/globals"; +import { percentageChange } from "./percentageChange"; + +describe("percentageChange", () => { + test("simple", async () => { + expect( + percentageChange({ + current: 10, + previous: 12, + }) + ).toBe(-16.67); + expect( + percentageChange({ + current: 0, + previous: 12, + }) + ).toBe(0); + expect( + percentageChange({ + current: 0, + previous: 0, + }) + ).toBe(0); + expect( + percentageChange({ + current: 99, + previous: 0, + }) + ).toBe(0); + }); +}); diff --git a/src/math/percentageChange.ts b/src/math/percentageChange.ts new file mode 100644 index 0000000..3b5dfd5 --- /dev/null +++ b/src/math/percentageChange.ts @@ -0,0 +1,16 @@ +import { isPositiveInt } from "../validators"; + +export const percentageChange = ({ + previous, + current, +}: { + previous: number; + current: number; +}): number => { + if (!isPositiveInt(previous) || !isPositiveInt(current)) return 0; + if (current === 0 && previous === 0) return 0; + if (current === 0 && previous !== 0) return -100; + if (current !== 0 && previous === 0) return 100; + const perChange = ((current - previous) * 100) / previous; + return parseFloat(perChange.toFixed(2)); +}; diff --git a/src/prisma/checkIsPrismaRefMissingError.ts b/src/prisma/checkIsPrismaRefMissingError.ts new file mode 100644 index 0000000..9f2b4a8 --- /dev/null +++ b/src/prisma/checkIsPrismaRefMissingError.ts @@ -0,0 +1,18 @@ +/** + * Useful for no-op queries where you want to make sure the error is a unique constraint error. + * @example + * try { + * await query(); + * } catch (e) { + * checkIsPrismaUniqueConstraintError(e); + * // carry on with other stuff, it is a unique constraint error + * } + * @link https://www.prisma.io/docs/concepts/components/prisma-client/handling-exceptions-and-errors + * @link https://www.prisma.io/docs/reference/api-reference/error-reference#p2002 + */ +export const checkIsPrismaRefMissingError = (error: Error) => { + if (!isPrismaRefMissingError(error)) throw error; +}; + +export const isPrismaRefMissingError = (error: Error & { code?: string }) => + error.code === "P2025"; diff --git a/src/prisma/checkIsPrismaUniqueConstraintError.ts b/src/prisma/checkIsPrismaUniqueConstraintError.ts new file mode 100644 index 0000000..3d46edb --- /dev/null +++ b/src/prisma/checkIsPrismaUniqueConstraintError.ts @@ -0,0 +1,19 @@ +/** + * Useful for no-op queries where you want to make sure the error is a unique constraint error. + * @example + * try { + * await query(); + * } catch (e) { + * checkIsPrismaUniqueConstraintError(e); + * // carry on with other stuff, it is a unique constraint error + * } + * @link https://www.prisma.io/docs/concepts/components/prisma-client/handling-exceptions-and-errors + * @link https://www.prisma.io/docs/reference/api-reference/error-reference#p2002 + */ +export const checkIsPrismaUniqueConstraintError = (error: Error) => { + if (!isPrismaUniqueConstraintError(error)) throw error; +}; + +export const isPrismaUniqueConstraintError = ( + error: Error & { code?: string } +) => error.code === "P2002"; diff --git a/src/prisma/index.ts b/src/prisma/index.ts new file mode 100644 index 0000000..b3d8044 --- /dev/null +++ b/src/prisma/index.ts @@ -0,0 +1 @@ +export * from "./prismaDateRange"; diff --git a/src/prisma/prismaDateRange.ts b/src/prisma/prismaDateRange.ts new file mode 100644 index 0000000..d0dece5 --- /dev/null +++ b/src/prisma/prismaDateRange.ts @@ -0,0 +1,16 @@ +import { parseDate } from "../helpers/parseDate"; +import { DateRange } from "../types/Date"; + +export const prismaDateRange = ({ startDate, endDate }: DateRange) => { + const gte = parseDate(startDate); + const lt = parseDate(endDate); + + if (!gte || !lt) { + throw new Error("prismaDateRange: Invalid date range"); + } + + return { + gte, + lt, + }; +}; diff --git a/src/trpc/formatTrpcInputQueryString.ts b/src/trpc/formatTrpcInputQueryString.ts new file mode 100644 index 0000000..af88c48 --- /dev/null +++ b/src/trpc/formatTrpcInputQueryString.ts @@ -0,0 +1,7 @@ +import { PlainObject } from "../types"; + +export const formatTrpcInputQueryString = (input: PlainObject) => { + return new URLSearchParams({ + input: JSON.stringify(input), + }); +}; diff --git a/src/trpc/index.ts b/src/trpc/index.ts new file mode 100644 index 0000000..01845f6 --- /dev/null +++ b/src/trpc/index.ts @@ -0,0 +1 @@ +export * from "./formatTrpcInputQueryString";