diff --git a/packages/utilities/package.json b/packages/utilities/package.json index 3a0d3e0..eabb6d0 100644 --- a/packages/utilities/package.json +++ b/packages/utilities/package.json @@ -1,6 +1,6 @@ { "name": "@shiftcode/utilities", - "version": "2.0.1", + "version": "2.1.0-pr16.1", "description": "Contains some utilities", "repository": "https://github.com/shiftcode/sc-commons-public", "license": "MIT", diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index ef257f8..417e7f5 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -7,9 +7,15 @@ export * from './lib/group-by/group-by.js' export * from './lib/http/http-constants.js' export * from './lib/json-stringify-replacer/json-stringify-replacer.function.js' export * from './lib/map-values-deep/map-values-deep.js' -export * from './lib/random-int/random-int.js' +export * from './lib/object/omit-props.function.js' +export * from './lib/object/pick-props.function.js' +export * from './lib/promise/make-deferred.function.js' +export * from './lib/math/clamp.function.js' +export * from './lib/math/random-int.js' export * from './lib/tag/get-tag.function.js' export * from './lib/tag/tag.enum.js' export * from './lib/type-utils/allowed-names.type.js' export * from './lib/type-utils/nullable.type.js' -export * from './lib/words/words.js' +export * from './lib/type-utils/union-to-intersection.type.js' +export * from './lib/string/words.js' +export * from './lib/string/capitalize.function.js' diff --git a/packages/utilities/src/lib/http/http-constants.ts b/packages/utilities/src/lib/http/http-constants.ts index 9d0207e..bd2ae93 100644 --- a/packages/utilities/src/lib/http/http-constants.ts +++ b/packages/utilities/src/lib/http/http-constants.ts @@ -67,6 +67,10 @@ export enum ContentType { JSON = 'application/json', TXT = 'text/plain', XML = 'application/xml', + CSV = 'text/csv', + DOCX = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + XLSX = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + PPTX = 'application/vnd.openxmlformats-officedocument.presentationml.presentation', } export enum CorsHeader { @@ -92,6 +96,7 @@ export enum HttpMethod { export enum CommonHttpHeader { CONTENT_TYPE = 'Content-Type', + CONTENT_DISPOSITION = 'Content-Disposition', VARY = 'Vary', CONTENT_LENGTH = 'Content-Length', AUTHORIZATION = 'Authorization', diff --git a/packages/utilities/src/lib/math/clamp.function.spec.ts b/packages/utilities/src/lib/math/clamp.function.spec.ts new file mode 100644 index 0000000..1fa6061 --- /dev/null +++ b/packages/utilities/src/lib/math/clamp.function.spec.ts @@ -0,0 +1,18 @@ +import { clamp } from './clamp.function.js' + +describe('clamp', () => { + test('returns val when in range', () => { + expect(clamp(0, 1, 2)).toBe(1) + expect(clamp(-10, -7, -5)).toBe(-7) + }) + + test('returns min when val smaller than min', () => { + expect(clamp(0, -1, 2)).toBe(0) + expect(clamp(-10, -20, -5)).toBe(-10) + }) + + test('returns max when val bigger than max', () => { + expect(clamp(0, 3, 2)).toBe(2) + expect(clamp(-10, 0, -5)).toBe(-5) + }) +}) diff --git a/packages/utilities/src/lib/math/clamp.function.ts b/packages/utilities/src/lib/math/clamp.function.ts new file mode 100644 index 0000000..aa7418a --- /dev/null +++ b/packages/utilities/src/lib/math/clamp.function.ts @@ -0,0 +1,11 @@ +/** + * returns the value clamped to the inclusive range of min and max + * @example ```ts + * clamp(0, 2, 5) // 2 + * clamp(0, 10, 5) // 5 + * clamp(0, -3, 5) // 0 + * ``` + */ +export function clamp(min: number, value: number, max: number): number { + return Math.max(min, Math.min(max, value)) +} diff --git a/packages/utilities/src/lib/random-int/random-int.ts b/packages/utilities/src/lib/math/random-int.ts similarity index 100% rename from packages/utilities/src/lib/random-int/random-int.ts rename to packages/utilities/src/lib/math/random-int.ts diff --git a/packages/utilities/src/lib/object/omit-props.function.spec.ts b/packages/utilities/src/lib/object/omit-props.function.spec.ts new file mode 100644 index 0000000..8c05558 --- /dev/null +++ b/packages/utilities/src/lib/object/omit-props.function.spec.ts @@ -0,0 +1,23 @@ +import { omitProps } from './omit-props.function.js' + +describe('omitProps', () => { + test('removes the specified props from given object', () => { + const res = omitProps({ a: true, b: false, c: 'ok' }, ['a']) + expect(res).toEqual({ b: false, c: 'ok' }) + }) + + test('does not alter original object', () => { + const orig = Object.freeze({ foo: 'bar', hello: 'world', a: true, b: false }) + expect(() => omitProps(orig, ['a', 'b'])).not.toThrow() + }) + + test('is typesafe', () => { + type A = { a: boolean; b: string } + type B = { a: boolean; b?: number } + const fn = (arg: B) => arg.a + const value: A = { a: true, b: 'foo bar' } + // if omitProps would not propertly remove `b` from the type, it would not be assignable + expect(fn(omitProps(value, ['b']))).toBe(true) + }) + +}) diff --git a/packages/utilities/src/lib/object/omit-props.function.ts b/packages/utilities/src/lib/object/omit-props.function.ts new file mode 100644 index 0000000..0b97009 --- /dev/null +++ b/packages/utilities/src/lib/object/omit-props.function.ts @@ -0,0 +1,9 @@ +/** + * returns a new object with all properties but the provided. + */ +export function omitProps( + obj: T, + props: readonly K[], +): Omit { + return Object.fromEntries(Object.entries(obj).filter(([k]) => !props.includes(k))) +} diff --git a/packages/utilities/src/lib/object/pick-props.function.spec.ts b/packages/utilities/src/lib/object/pick-props.function.spec.ts new file mode 100644 index 0000000..c52cca1 --- /dev/null +++ b/packages/utilities/src/lib/object/pick-props.function.spec.ts @@ -0,0 +1,20 @@ +import { pickProps } from './pick-props.function.js' + +describe('pickProps', () => { + + test('picks the properties accordingly', () => { + const obj = { a: true, b: 'ok', c: { foo: { bar: 'baz' } }, not: false } + const res = pickProps(obj, ['a', 'b', 'c']) + expect(Object.keys(res)).toEqual(['a', 'b', 'c']) + expect(res.a).toBe(true) + expect(res.b).toBe('ok') + expect(res.c).toBe(obj.c) + }) + + test('does not alter the provided object', () => { + const obj = { a: true, b: 'ok', c: null } + Object.freeze(obj) // freeze so it would throw if altered + expect(() => pickProps(obj, ['a', 'b'])).not.toThrow() + }) + +}) diff --git a/packages/utilities/src/lib/object/pick-props.function.ts b/packages/utilities/src/lib/object/pick-props.function.ts new file mode 100644 index 0000000..6b31f98 --- /dev/null +++ b/packages/utilities/src/lib/object/pick-props.function.ts @@ -0,0 +1,10 @@ +export type PickedPropsOrNull = { + [key in K]-?: T[key] extends null ? T[key] | null : T extends undefined ? T[key] | null : T[key] +} + +/** + * returns a new object containing only the provided props with their respective values or null when not defined + */ +export function pickProps(obj: T, props: readonly K[]): PickedPropsOrNull { + return Object.fromEntries(props.map((p) => [p, obj[p] ?? null])) +} diff --git a/packages/utilities/src/lib/promise/make-deferred.function.spec.ts b/packages/utilities/src/lib/promise/make-deferred.function.spec.ts new file mode 100644 index 0000000..bb8b3ba --- /dev/null +++ b/packages/utilities/src/lib/promise/make-deferred.function.spec.ts @@ -0,0 +1,22 @@ +import { makeDeferred } from './make-deferred.function.js' + +describe('makeDeferred', () => { + + test('returns an object containing a promise', () => { + const { promise } = makeDeferred() + expect(promise).toBeInstanceOf(Promise) + }) + + test('resolves the returned promise with the returned resolveFn', async () => { + const { promise, resolve } = makeDeferred() + resolve('foo') + await expect(promise).resolves.toBe('foo') + }) + + test('rejects the returned promise with the returned rejectFn', async () => { + const { promise, reject } = makeDeferred() + reject(new Error('Foo Bar')) + await expect(promise).rejects.toThrow('Foo Bar') + }) + +}) \ No newline at end of file diff --git a/packages/utilities/src/lib/promise/make-deferred.function.ts b/packages/utilities/src/lib/promise/make-deferred.function.ts new file mode 100644 index 0000000..e3a21a4 --- /dev/null +++ b/packages/utilities/src/lib/promise/make-deferred.function.ts @@ -0,0 +1,30 @@ +export interface Deferred { + resolve: (value: T | PromiseLike) => void + reject: (reason: unknown) => void + promise: Promise +} + +/** + * returns a `deferred` object which contains a promise and its resolve/reject functions + * @example ```ts + * const deferred = new makeDeferred() + * + * // use promise + * deferred.promise.then(console.log, console.error) + * + * // resolve + * deferred.resolve('a value') + * + * // reject + * deferred.reject(new Error('oh no!')) + * ``` + */ +export function makeDeferred(): Deferred { + const deferred: Deferred = {} + + deferred.promise = new Promise((res, rej) => { + deferred.resolve = res + deferred.reject = rej + }) + return deferred +} diff --git a/packages/utilities/src/lib/string/capitalize.function.spec.ts b/packages/utilities/src/lib/string/capitalize.function.spec.ts new file mode 100644 index 0000000..0903e83 --- /dev/null +++ b/packages/utilities/src/lib/string/capitalize.function.spec.ts @@ -0,0 +1,13 @@ +import { capitalize } from './capitalize.function.js' + +describe('capitalize', () => { + test('when string length > 1', () => { + expect(capitalize('shiftcode')).toEqual('Shiftcode') + }) + test('when string length = 1', () => { + expect(capitalize('s')).toEqual('S') + }) + test('when string length < 1', () => { + expect(capitalize('')).toEqual('') + }) +}) diff --git a/packages/utilities/src/lib/string/capitalize.function.ts b/packages/utilities/src/lib/string/capitalize.function.ts new file mode 100644 index 0000000..645d975 --- /dev/null +++ b/packages/utilities/src/lib/string/capitalize.function.ts @@ -0,0 +1,3 @@ +export function capitalize(val: string): string { + return val.charAt(0).toUpperCase() + val.slice(1) +} diff --git a/packages/utilities/src/lib/words/words.spec.ts b/packages/utilities/src/lib/string/words.spec.ts similarity index 100% rename from packages/utilities/src/lib/words/words.spec.ts rename to packages/utilities/src/lib/string/words.spec.ts diff --git a/packages/utilities/src/lib/words/words.ts b/packages/utilities/src/lib/string/words.ts similarity index 100% rename from packages/utilities/src/lib/words/words.ts rename to packages/utilities/src/lib/string/words.ts diff --git a/packages/utilities/src/lib/type-utils/union-to-intersection.type.ts b/packages/utilities/src/lib/type-utils/union-to-intersection.type.ts new file mode 100644 index 0000000..9d3cf87 --- /dev/null +++ b/packages/utilities/src/lib/type-utils/union-to-intersection.type.ts @@ -0,0 +1,7 @@ +/** + * transforms a union type to an intersection type + * for explanation see here: https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type/50375286#50375286 + * @example + * type X = UnionToIntersection<{ a: boolean } | { b: string }> // --> {a: boolean } & { b: string } + */ +export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never