diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..5428c4d --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,2 @@ +exclude_patterns: +- "**/*.spec.ts" \ No newline at end of file diff --git a/LICENSE b/LICENSE index a1b8d92..8760c1d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) Patrick Michalina +Copyright (c) Patrick Michalina 2018 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package-lock.json b/package-lock.json index ff9f4f9..682c0b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1354,9 +1354,9 @@ "dev": true }, "@types/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-UoOfVEzAUpeSPmjm7h1uk5MH6KZma2z2O7a75onTGjnNvAvMVrPzPL/vBbT65iIGHWj6rokwfmYcmxmlSf2uwg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.0.tgz", + "integrity": "sha512-xCbDUSZArlmMjiJdczt8AFNH2MwcMb/pj/HKja1hx3u1qzOUINcJktQMGoGVlgFnzxnuCahxKFlcRBkSAcm33g==", "dev": true, "requires": { "@types/node": "*" @@ -1397,9 +1397,9 @@ } }, "@types/jest": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.1.tgz", - "integrity": "sha512-msra1bCaAeEdkSyA0CZ6gW1ukMIvZ5YoJkdXw/qhQdsuuDlFTcEUrUw8CLCPt2rVRUfXlClVvK2gvPs9IokZaA==", + "version": "25.2.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.2.tgz", + "integrity": "sha512-aRctFbG8Pb7DSLzUt/fEtL3q/GKb9mretFuYhRub2J0q6NhzBYbx9HTQzHrWgBNIxYOlxGNVe6Z54cpbUt+Few==", "dev": true, "requires": { "jest-diff": "^25.2.1", @@ -10360,9 +10360,9 @@ } }, "rollup": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.10.0.tgz", - "integrity": "sha512-7BmpEfUN9P6esJzWIn3DmR//90mW6YwYB1t3y48LpF8ITpYtL8s1kEirMKqUu44dVH/6a/rs0EuwYVL3FuRDoA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.10.2.tgz", + "integrity": "sha512-tivFM8UXBlYOUqpBYD3pRktYpZvK/eiCQ190eYlrAyrpE/lzkyG2gbawroNdbwmzyUc7Y4eT297xfzv0BDh9qw==", "dev": true, "requires": { "fsevents": "~2.1.2" @@ -11488,9 +11488,9 @@ "dev": true }, "ts-jest": { - "version": "25.5.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.5.1.tgz", - "integrity": "sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.0.0.tgz", + "integrity": "sha512-eBpWH65mGgzobuw7UZy+uPP9lwu+tPp60o324ASRX4Ijg8UC5dl2zcge4kkmqr2Zeuk9FwIjvCTOPuNMEyGWWw==", "dev": true, "requires": { "bs-logger": "0.x", @@ -11500,9 +11500,23 @@ "lodash.memoize": "4.x", "make-error": "1.x", "micromatch": "4.x", - "mkdirp": "0.x", - "semver": "6.x", + "mkdirp": "1.x", + "semver": "7.x", "yargs-parser": "18.x" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } } }, "ts-node": { diff --git a/package.json b/package.json index 0e2dad4..5d99070 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,9 @@ "typings": "index.d.ts", "sideEffects": false, "author": "Patrick Michalina (https://patrickmichalina.com)", + "contributors": [ + "Williama Reynolds" + ], "license": "MIT", "repository": { "type": "git", @@ -35,8 +38,8 @@ }, "devDependencies": { "@rollup/plugin-typescript": "^3.1.1", - "@types/fs-extra": "^8.1.0", - "@types/jest": "^25.2.1", + "@types/fs-extra": "^9.0.0", + "@types/jest": "^25.2.2", "@types/node": "^14.0.1", "codecov": "^3.6.5", "fast-check": "^1.24.2", @@ -44,18 +47,18 @@ "istanbul": "^0.4.5", "jest": "26.0.1", "jest-junit": "^10.0.0", - "rollup": "^2.10.0", + "rollup": "^2.10.2", "rxjs": "^6.5.4", "semantic-release": "^17.0.7", "terser": "^4.6.13", - "ts-jest": "^25.5.1", + "ts-jest": "^26.0.0", "ts-node": "^8.10.1", "tslint": "^6.1.2", "tslint-immutable": "^6.0.1", "typescript": "^3.9.2" }, "peerDependencies": { - "tslib": "^1.12.0", + "tslib": "^2.0.0", "rxjs": "^6.5" }, "jest": { @@ -70,7 +73,7 @@ "coverageThreshold": { "global": { "branches": 100, - "functions": 95, + "functions": 100, "lines": 100, "statements": 100 } @@ -80,7 +83,8 @@ }, "testPathIgnorePatterns": [ "/node_modules/", - "/dist/" + "/dist/", + "public_api.ts" ], "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(ts?)$", "moduleFileExtensions": [ @@ -92,4 +96,4 @@ "node" ] } -} +} \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js index 9aee483..99d38cb 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -20,14 +20,14 @@ export default [ ] }, { - input: 'src/index.ts', - output: [ - { file: `dist/${pkg.module}`, format: 'es', sourcemap: true }, - { file: `dist/${pkg.commonJs}`, format: 'cjs', sourcemap: true } - ], - external: [ - 'rxjs', - 'rxjs/operators' - ], - plugins: [typescript()] -}] + input: 'src/index.ts', + output: [ + { file: `dist/${pkg.module}`, format: 'es', sourcemap: true }, + { file: `dist/${pkg.commonJs}`, format: 'cjs', sourcemap: true } + ], + external: [ + 'rxjs', + 'rxjs/operators' + ], + plugins: [typescript()] + }] diff --git a/src/either/either.factory.ts b/src/either/either.factory.ts new file mode 100644 index 0000000..97e799c --- /dev/null +++ b/src/either/either.factory.ts @@ -0,0 +1,5 @@ +import { Either } from './either' + +export function either(left?: L, right?: R) { + return new Either(left, right) +} \ No newline at end of file diff --git a/src/interfaces/either.interface.ts b/src/either/either.interface.ts similarity index 100% rename from src/interfaces/either.interface.ts rename to src/either/either.interface.ts diff --git a/test/monads/either.spec.ts b/src/either/either.spec.ts similarity index 99% rename from test/monads/either.spec.ts rename to src/either/either.spec.ts index 1caa684..0d447b8 100644 --- a/test/monads/either.spec.ts +++ b/src/either/either.spec.ts @@ -1,4 +1,4 @@ -import { either } from '../../src' +import { either } from './either.factory' describe(either.name, () => { it('when calling should throw if both sides are defined', () => { diff --git a/src/either/either.ts b/src/either/either.ts new file mode 100644 index 0000000..4216549 --- /dev/null +++ b/src/either/either.ts @@ -0,0 +1,56 @@ +import { IEitherPattern, IEither } from './either.interface' + +export class Either implements IEither { + constructor(private readonly left?: L, private readonly right?: R) { + if (this.neitherExist()) { + throw new TypeError('Either requires a left or a right') + } + if (this.bothExist()) { + throw new TypeError('Either cannot have both a left and a right') + } + } + + private static exists(value?: T): boolean { + return typeof value !== 'undefined' && value !== null + } + + private bothExist(): boolean { + return this.isLeft() && this.isRight() + } + + private neitherExist(): boolean { + return !this.isLeft() && !this.isRight() + } + + public isLeft(): boolean { + return Either.exists(this.left) + } + + public isRight(): boolean { + return Either.exists(this.right) + } + + public match(pattern: IEitherPattern): T { + return this.isRight() + ? pattern.right(this.right as R) + : pattern.left(this.left as L) + } + + public tap(pattern: Partial>): void { + this.isRight() + ? typeof pattern.right === 'function' && pattern.right(this.right as R) + : typeof pattern.left === 'function' && pattern.left(this.left as L) + } + + public map(fn: (r: R) => T): IEither { + return this.isRight() + ? new Either(undefined, fn(this.right as R)) + : new Either(this.left) + } + + public flatMap(fn: (r: R) => IEither): IEither { + return this.isRight() + ? fn(this.right as R) + : new Either(this.left) + } +} \ No newline at end of file diff --git a/src/either/public_api.ts b/src/either/public_api.ts new file mode 100644 index 0000000..02592ae --- /dev/null +++ b/src/either/public_api.ts @@ -0,0 +1,3 @@ +export * from './either' +export * from './either.factory' +export * from './either.interface' \ No newline at end of file diff --git a/src/index.spec.ts b/src/index.spec.ts new file mode 100644 index 0000000..3df8979 --- /dev/null +++ b/src/index.spec.ts @@ -0,0 +1,20 @@ +import { maybe, Maybe, either, Either, ok, fail, Result, reader, Reader } from './index' + +describe('package api', () => { + it('should export maybe', () => { + expect(maybe(1)).toBeInstanceOf(Maybe) + }) + + it('should export either', () => { + expect(either(1)).toBeInstanceOf(Either) + }) + + it('should export result', () => { + expect(ok(1)).toBeInstanceOf(Result) + expect(fail(1)).toBeInstanceOf(Result) + }) + + it('should export reader', () => { + expect(reader(_cfg => 1)).toBeInstanceOf(Reader) + }) +}) \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index a72fb33..24935a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -export * from './monads/index' -export * from './interfaces/index' -export { - maybeToObservable -} from './util/index' +export * from './maybe/public_api' +export * from './reader/public_api' +export * from './either/public_api' +export * from './result/public_api' +export * from './monad/public_api' \ No newline at end of file diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts deleted file mode 100644 index a0e75ed..0000000 --- a/src/interfaces/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './either.interface' -export * from './maybe.interface' -export * from './monad.interface' -export * from './reader.interface' diff --git a/src/interfaces/monad.interface.ts b/src/interfaces/monad.interface.ts deleted file mode 100644 index d3cf49e..0000000 --- a/src/interfaces/monad.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -// tslint:disable:readonly-array -export type mapping = (x: T, ...args: any[]) => U - -export interface IMonad { - of(x: T, ...args: any[]): IMonad - flatMap(f: mapping>): IMonad -} diff --git a/src/interfaces/reader.interface.ts b/src/interfaces/reader.interface.ts deleted file mode 100644 index 025727a..0000000 --- a/src/interfaces/reader.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface IReader { - of(fn: (config: E) => A): IReader, - run(config: E): A, - map(fn: (val: A) => B): IReader - flatMap(fn: (val: A) => IReader): IReader, -} \ No newline at end of file diff --git a/src/interfaces/result.interface.ts b/src/interfaces/result.interface.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/maybe/maybe.factory.spec.ts b/src/maybe/maybe.factory.spec.ts new file mode 100644 index 0000000..48cc806 --- /dev/null +++ b/src/maybe/maybe.factory.spec.ts @@ -0,0 +1,16 @@ +import { maybe, none, some } from './maybe.factory' + +describe('should construct maybes', () => { + it('should handle "maybe" case', () => { + const sut = 'asdasd' as string | undefined + expect(maybe(sut).isSome()).toEqual(true) + }) + + it('should handle "none" case', () => { + expect(none().isNone()).toEqual(true) + }) + + it('should handle "some" case', () => { + expect(some('test').isSome()).toEqual(true) + }) +}) \ No newline at end of file diff --git a/src/maybe/maybe.factory.ts b/src/maybe/maybe.factory.ts new file mode 100644 index 0000000..2d44cc5 --- /dev/null +++ b/src/maybe/maybe.factory.ts @@ -0,0 +1,13 @@ +import { Maybe } from './maybe' + +export function maybe(value?: T) { + return new Maybe(value) +} + +export function none() { + return Maybe.none() +} + +export function some(value: T) { + return maybe(value) +} diff --git a/src/interfaces/maybe.interface.ts b/src/maybe/maybe.interface.ts similarity index 93% rename from src/interfaces/maybe.interface.ts rename to src/maybe/maybe.interface.ts index 6395e4d..55c841f 100644 --- a/src/interfaces/maybe.interface.ts +++ b/src/maybe/maybe.interface.ts @@ -1,4 +1,4 @@ -import { IMonad } from './monad.interface' +import { IMonad } from '../monad/monad.interface' /** * Define a contract to unwrap Maybe object @@ -21,7 +21,7 @@ export interface IMaybePattern { export interface IMaybe extends IMonad { // tslint:disable-next-line:readonly-array - of(x?: T, ...args: any[]): IMaybe + of(x: T, ...args: any[]): IMaybe /** * Unwrap a Maybe with a default value @@ -96,7 +96,7 @@ export interface IMaybe extends IMonad { /** * Combine multiple Maybe, automatically wrapping predicate */ - flatMapAuto(f: (t: T) => R): IMaybe> + flatMapAuto(fn: (v: T) => R): IMaybe> /** * Apply a predicate which if met, continues the Maybe chain, diff --git a/test/monads/maybe.spec.ts b/src/maybe/maybe.spec.ts similarity index 94% rename from test/monads/maybe.spec.ts rename to src/maybe/maybe.spec.ts index 3c1f0fe..dc7cb3c 100644 --- a/test/monads/maybe.spec.ts +++ b/src/maybe/maybe.spec.ts @@ -1,4 +1,5 @@ -import { maybe, IMaybe, maybeToPromise } from '../../src' +import { maybe } from './public_api' +import { Maybe } from './maybe' describe('Maybe', () => { describe('when returning a value with possible throw', () => { @@ -289,7 +290,7 @@ describe('Maybe', () => { const nsut = undefined as number | undefined const maybeSomeNumber = maybe(sut) - .flatMap(() => maybe(nsut)) + .flatMap(a => maybe(nsut)) .valueOr(1) expect(maybeSomeNumber).toEqual(1) @@ -498,35 +499,6 @@ describe('Maybe', () => { }) }) - describe('maybeToPromise', () => { - it('should flatmap', () => { - const sut = new Promise>((a, b) => a(maybe('test'))) - - sut - .then(maybeToPromise()) - .then(result => expect(result).toEqual('test')) - .catch(_shouldNotBeHere => expect(false).toBe(true)) - }) - - it('should catch w/ default message', () => { - const sut = new Promise>((a, b) => a(maybe())) - - sut - .then(maybeToPromise()) - .then(_shouldNotBeHere => expect(false).toBe(true)) - .catch(error => expect(error).toEqual('not found')) - }) - - it('should catch w/ custom message', () => { - const sut = new Promise>((a, b) => a(maybe())) - - sut - .then(maybeToPromise('err')) - .then(_shouldNotBeHere => expect(false).toBe(true)) - .catch(error => expect(error).toEqual('err')) - }) - }) - describe('apply', () => { it('should return none in nullish cases', () => { const thisNone = maybe() @@ -545,4 +517,10 @@ describe('Maybe', () => { expect(a.apply(f).valueOrThrow()).toBe(10) }) }) + + describe('static', () => { + it('should return new maybe with some', () => { + expect(Maybe.some(1).valueOrThrowErr()).toEqual(1) + }) + }) }) diff --git a/src/maybe/maybe.ts b/src/maybe/maybe.ts new file mode 100644 index 0000000..e096559 --- /dev/null +++ b/src/maybe/maybe.ts @@ -0,0 +1,104 @@ +import { IMaybePattern, IMaybe } from './maybe.interface' + +export class Maybe implements IMaybe { + + constructor(private readonly value?: T) { } + + public of(value: T): IMaybe { + return new Maybe(value) + } + + public static none() { + return new Maybe() + } + + public static some(value: T) { + return new Maybe(value) + } + + public isSome(): boolean { + return !this.isNone() + } + + public isNone(): boolean { + return this.value === null || this.value === undefined + } + + public valueOr(value: NonNullable): NonNullable { + return this.isSome() ? this.value as NonNullable : value + } + + public valueOrUndefined(): T | undefined { + return this.isSome() ? this.value as NonNullable : undefined + } + + public valueOrCompute(fn: () => NonNullable): NonNullable { + return this.isSome() ? this.value as NonNullable : fn() + } + + public valueOrThrow(msg?: string): NonNullable { + return this.isNone() ? (() => { throw new Error(msg) })() : this.value as NonNullable + } + + public valueOrThrowErr(err?: Error): NonNullable { + return this.isNone() + ? (() => err instanceof Error ? (() => { throw err })() : (() => { throw new Error() })())() + : this.value as NonNullable + } + + public tap(obj: Partial>) { + return this.isNone() + ? typeof obj.none === 'function' && obj.none() + : typeof obj.some === 'function' && obj.some(this.value as NonNullable) + } + + public tapNone(fn: () => void): void { + (this.isNone()) && fn() + } + + public tapSome(fn: (val: NonNullable) => void): void { + (this.isSome()) && fn(this.value as NonNullable) + } + + public match(pattern: IMaybePattern) { + return this.isNone() + ? pattern.none() + : pattern.some(this.value as NonNullable) + } + + public toArray() { + return this.isNone() + ? [] + : Array.isArray(this.value) + ? this.value + : [this.value as NonNullable] + } + + public map(fn: (t: NonNullable) => R): IMaybe { + return this.isSome() + ? new Maybe(fn(this.value as NonNullable)) + : new Maybe() + } + + public flatMap(fn: (d: NonNullable) => IMaybe): IMaybe { + return this.isNone() ? new Maybe() : fn(this.value as NonNullable) + } + + public flatMapAuto(fn: (d: NonNullable) => R): IMaybe> { + return this.isNone() + ? new Maybe>() + : new Maybe>(fn(this.value as NonNullable) as NonNullable) + } + + public filter(fn: (f: NonNullable) => boolean): IMaybe { + return this.isNone() + ? new Maybe() + : fn(this.value as NonNullable) + ? new Maybe(this.value as NonNullable) + : new Maybe() + } + + public apply(fab: IMaybe<(v: T) => R>): IMaybe { + return this.flatMap(b => fab.map(fn => fn(b))) + } +} diff --git a/src/maybe/public_api.ts b/src/maybe/public_api.ts new file mode 100644 index 0000000..11a034b --- /dev/null +++ b/src/maybe/public_api.ts @@ -0,0 +1,5 @@ +export * from './maybe' +export * from './maybe.factory' +export * from './maybe.interface' +export * from './transformers/maybe-to-promise' +export * from './transformers/maybe-to-observable' \ No newline at end of file diff --git a/test/util/maybe/maybe-to-observable.spec.ts b/src/maybe/transformers/maybe-to-observable.spec.ts similarity index 88% rename from test/util/maybe/maybe-to-observable.spec.ts rename to src/maybe/transformers/maybe-to-observable.spec.ts index d122ee3..2de6347 100644 --- a/test/util/maybe/maybe-to-observable.spec.ts +++ b/src/maybe/transformers/maybe-to-observable.spec.ts @@ -1,8 +1,7 @@ import { assert, integer, property } from 'fast-check' -import { maybe } from '../../../src/monads' -import { maybeToObservable } from '../../../src/util' import { merge, of } from 'rxjs' import { count } from 'rxjs/operators' +import { maybe, maybeToObservable } from '../public_api' describe('maybeToObservable', () => { const numRuns = 100 diff --git a/src/util/maybe/maybe-to-observable.ts b/src/maybe/transformers/maybe-to-observable.ts similarity index 94% rename from src/util/maybe/maybe-to-observable.ts rename to src/maybe/transformers/maybe-to-observable.ts index 2befbf9..ed4eac2 100644 --- a/src/util/maybe/maybe-to-observable.ts +++ b/src/maybe/transformers/maybe-to-observable.ts @@ -1,6 +1,6 @@ -import { IMaybe } from '../../interfaces' import { EMPTY, Observable, of } from 'rxjs' import { take } from 'rxjs/operators' +import { IMaybe } from '../maybe.interface' /** * Convert a Maybe into an observable diff --git a/src/maybe/transformers/maybe-to-promise.spec.ts b/src/maybe/transformers/maybe-to-promise.spec.ts new file mode 100644 index 0000000..1691d64 --- /dev/null +++ b/src/maybe/transformers/maybe-to-promise.spec.ts @@ -0,0 +1,32 @@ +import { IMaybe } from '../maybe.interface' +import { maybeToPromise } from './maybe-to-promise' +import { maybe } from '../public_api' + +describe('maybeToPromise', () => { + it('should flatmap', () => { + const sut = new Promise>((a, b) => a(maybe('test'))) + + sut + .then(maybeToPromise()) + .then(result => expect(result).toEqual('test')) + .catch(_shouldNotBeHere => expect(false).toBe(true)) + }) + + it('should catch w/ default message', () => { + const sut = new Promise>((a, b) => a(maybe())) + + sut + .then(maybeToPromise()) + .then(_shouldNotBeHere => expect(false).toBe(true)) + .catch(error => expect(error).toEqual('not found')) + }) + + it('should catch w/ custom message', () => { + const sut = new Promise>((a, b) => a(maybe())) + + sut + .then(maybeToPromise('err')) + .then(_shouldNotBeHere => expect(false).toBe(true)) + .catch(error => expect(error).toEqual('err')) + }) +}) \ No newline at end of file diff --git a/src/maybe/transformers/maybe-to-promise.ts b/src/maybe/transformers/maybe-to-promise.ts new file mode 100644 index 0000000..e91ffbe --- /dev/null +++ b/src/maybe/transformers/maybe-to-promise.ts @@ -0,0 +1,7 @@ +import { IMaybe } from '../maybe.interface' + +export const maybeToPromise = + (catchResponse: any = 'not found') => + (maybe: IMaybe) => maybe.isSome() + ? Promise.resolve(maybe.valueOrUndefined() as T) + : Promise.reject(catchResponse) diff --git a/src/monad/monad.interface.ts b/src/monad/monad.interface.ts new file mode 100644 index 0000000..f778f7c --- /dev/null +++ b/src/monad/monad.interface.ts @@ -0,0 +1,6 @@ +export type Map = (x: T) => U + +export interface IMonad { + of(x: T): IMonad + flatMap(fn: Map>): IMonad +} diff --git a/src/monad/monad.ts b/src/monad/monad.ts new file mode 100644 index 0000000..90faf48 --- /dev/null +++ b/src/monad/monad.ts @@ -0,0 +1,6 @@ +import { IMonad, Map } from './monad.interface' + +export abstract class Monad implements IMonad { + abstract of(x: T): IMonad + abstract flatMap(fn: Map>): IMonad +} diff --git a/src/monad/public_api.ts b/src/monad/public_api.ts new file mode 100644 index 0000000..488f575 --- /dev/null +++ b/src/monad/public_api.ts @@ -0,0 +1,2 @@ +export * from './monad' +export * from './monad.interface' \ No newline at end of file diff --git a/src/monads/either.ts b/src/monads/either.ts deleted file mode 100644 index a880c1e..0000000 --- a/src/monads/either.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { IEither, IEitherPattern } from '../interfaces' - -const exists = (t: T) => t !== null && t !== undefined -const bothExist = (left?: L) => (right?: R) => exists(left) && exists(right) -const neitherExist = (left?: L) => (right?: R) => !exists(left) && !exists(right) -const existFunc = (side?: T) => () => exists(side) - -const tap = (left?: L) => (right?: R) => (pattern: Partial>) => - exists(right) - ? pattern.right && pattern.right(right as R) - : pattern.left && pattern.left(left as L) - -const match = (left?: L) => (right?: R) => (pattern: IEitherPattern) => exists(right) - ? pattern.right(right as R) - : pattern.left(left as L) - -const guardBothExist = (left?: L) => (right?: R) => { - // tslint:disable-next-line:no-if-statement - if (bothExist(left)(right)) { - throw new TypeError('Either cannot have both a left and a right') - } -} - -const guardNeitherExist = (left?: L) => (right?: R) => { - // tslint:disable-next-line:no-if-statement - if (neitherExist(left)(right)) { - throw new TypeError('Either requires a left or a right') - } -} - -const eitherGuards = (left?: L) => (right?: R) => { - guardBothExist(left)(right) - guardNeitherExist(left)(right) -} - -export const either = (left?: L, right?: R): IEither => { - eitherGuards(left)(right) - - return { - isLeft: existFunc(left), - isRight: existFunc(right), - match: match(left)(right), - tap: tap(left)(right), - map: (f: (r: R) => T) => exists(right) - ? either(undefined, f(right as R)) - : either(left), - flatMap: (f: (r: R) => IEither) => exists(right) ? - f(right as R) : - either(left) - } -} \ No newline at end of file diff --git a/src/monads/index.ts b/src/monads/index.ts deleted file mode 100644 index 9f55089..0000000 --- a/src/monads/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './monad' -export * from './maybe' -export * from './either' -export * from './reader' -export * from './result' \ No newline at end of file diff --git a/src/monads/maybe.ts b/src/monads/maybe.ts deleted file mode 100644 index 172283f..0000000 --- a/src/monads/maybe.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { IMaybe, IMaybePattern } from '../interfaces' - -const isEmpty = (value: T) => value === null || value === undefined -const isNotEmpty = (value: T) => !isEmpty(value) -const isSome = (value: T) => () => isNotEmpty(value) -const isNone = (value: T) => () => isEmpty(value) -const valueOr = (value?: T) => (val: NonNullable) => isEmpty(value) ? val : value as NonNullable -const valueOrUndefined = (value?: T) => () => isEmpty(value) ? undefined : value as NonNullable -const toArray = (value?: T) => () => isEmpty(value) ? [] : Array.isArray(value) ? value : [value as NonNullable] -const valueOrCompute = (value?: T) => (fn: () => NonNullable) => isEmpty(value) ? fn() : value as NonNullable -const tap = (value?: T) => (obj: Partial>) => isEmpty(value) ? obj.none && obj.none() : obj.some && obj.some(value as NonNullable) -const tapNone = (value?: T) => (fn: () => void) => (isEmpty(value)) && fn() -const tapSome = (value?: T) => (fn: (val: NonNullable) => void) => isNotEmpty(value) && fn(value as NonNullable) -const match = (value?: T) => (pattern: IMaybePattern) => isEmpty(value) ? pattern.none() : pattern.some(value as NonNullable) -const map = (value?: T) => (fn: (t: NonNullable) => R) => isEmpty(value) ? maybe() : maybe(fn(value as NonNullable)) -const flatMap = (value?: T) => (fn: (d: NonNullable) => IMaybe) => isEmpty(value) ? maybe() : fn(value as NonNullable) -const apply = (value?: T) => (maybeFn: IMaybe<(t: T) => R>) => maybeFn.flatMap(f => map(value)(f)) -const flatMapAuto = (value?: T) => (fn: (d: NonNullable) => R) => isEmpty(value) ? maybe() : maybe(fn(value as NonNullable)) -const valueOrThrow = (value?: T) => (msg?: string) => isEmpty(value) ? (() => { throw Error(msg) })() : value as NonNullable -const valueOrThrowErr = (value?: T) => (err?: Error) => - isEmpty(value) - ? (() => err instanceof Error ? (() => { throw err })() : (() => { throw Error() })())() - : value as NonNullable - -const filter = (value?: T) => - (fn: (d: NonNullable) => boolean) => - isEmpty(value) - ? maybe() - : fn(value as NonNullable) - ? maybe(value as NonNullable) - : maybe() - -export const maybe = (value?: T): IMaybe> => { - return { - of: maybe, - isSome: isSome(value), - isNone: isNone(value), - valueOr: valueOr(value), - valueOrUndefined: valueOrUndefined(value), - valueOrCompute: valueOrCompute(value), - valueOrThrow: valueOrThrow(value), - valueOrThrowErr: valueOrThrowErr(value), - toArray: toArray(value), - tap: tap(value), - tapNone: tapNone(value), - tapSome: tapSome(value), - match: match(value), - map: map(value), - flatMap: flatMap(value), - flatMapAuto: flatMapAuto(value), - filter: filter(value), - apply: apply(value) - } -} - -export const maybeToPromise = - (catchResponse: any = 'not found') => - (maybe: IMaybe) => maybe.isSome() - ? Promise.resolve(maybe.valueOrUndefined() as T) - : Promise.reject(catchResponse) diff --git a/src/monads/monad.ts b/src/monads/monad.ts deleted file mode 100644 index 0515304..0000000 --- a/src/monads/monad.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { mapping, IMonad } from '../interfaces' - -// tslint:disable:readonly-array -export const monad = (x: T, ...args: any[]): IMonad => { - return { - of: (x: T, ...args: any[]) => monad(x, ...args), - flatMap: (f: mapping>) => f(x, ...args) - } -} diff --git a/src/monads/reader.ts b/src/monads/reader.ts deleted file mode 100644 index f0cfea7..0000000 --- a/src/monads/reader.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IReader } from '../interfaces' - -// tslint:disable:no-this -export const reader = (fn: (config: E) => A): IReader => { - return { - of: (fn: (config: E) => A) => reader(fn), - run: (config: E) => fn(config), - map: function (fn: (val: A) => B) { - return reader(config => fn(this.run(config))) - }, - flatMap: function (fn: (val: A) => IReader) { - return reader(config => fn(this.run(config)).run(config)) - } - } -} diff --git a/src/monads/result.ts b/src/monads/result.ts deleted file mode 100644 index ea70ee3..0000000 --- a/src/monads/result.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { IMaybe } from '../interfaces' -import { maybe } from './maybe' - -const returnTrue = () => true -const returnFalse = () => false -const returnValue = (val: T) => () => val -const returnMaybe = (val: T) => () => maybe(val) -const throwReferenceError = (message: string) => () => { throw new ReferenceError(message) } - -type Predicate = () => boolean - -export interface IResultMatchPattern { - readonly ok: (val: T) => U - readonly fail: (val: E) => U -} - -export interface IResult { - isOk(): boolean - isFail(): boolean - maybeOk(): IMaybe - maybeFail(): IMaybe - unwrap(): T | never - unwrapOr(opt: T): T - unwrapFail(): E | never - match(fn: IResultMatchPattern): M - map(fn: (val: T) => M): IResult - mapFail(fn: (err: E) => M): IResult - flatMap(fn: (val: T) => IResult): IResult -} - -export interface IResultOk extends IResult { - unwrap(): T - unwrapOr(opt: T): T - unwrapFail(): never - match(fn: IResultMatchPattern): M - map(fn: (val: T) => M): IResultOk - mapFail(fn: (err: E) => M): IResultOk -} - -export interface IResultFail extends IResult { - unwrap(): never - unwrapOr(opt: T): T - unwrapFail(): E - match(fn: IResultMatchPattern): M - map(fn: (val: T) => M): IResultFail - mapFail(fn: (err: E) => M): IResultFail - flatMap(fn: (val: T) => IResult): IResultFail -} - -export const ok = (val: T): IResultOk => { - return { - isOk: returnTrue, - isFail: returnFalse, - maybeOk: returnMaybe(val), - maybeFail: maybe, - unwrap: returnValue(val), - unwrapOr: _ => val, - unwrapFail: throwReferenceError('Cannot unwrap a success'), - map: (fn: (val: T) => M) => ok(fn(val)), - mapFail: (_: (err: E) => M) => ok(val), - flatMap: (fn: (val: T) => IResult) => fn(val), - match: (fn: IResultMatchPattern) => fn.ok(val) - } -} - -export const fail = (err: E): IResultFail => { - return { - isOk: returnFalse, - isFail: returnTrue, - maybeOk: maybe, - maybeFail: returnMaybe(err), - unwrap: throwReferenceError('Cannot unwrap a failure'), - unwrapOr: opt => opt, - unwrapFail: returnValue(err), - map: (_: (val: T) => M) => fail(err), - mapFail: (fn: (err: E) => M) => fail(fn(err)), - flatMap: (_: (val: T) => IResult) => fail(err), - match: (fn: IResultMatchPattern) => fn.fail(err) - } -} - -/** - * Utility function to quickly create ok/fail pairs. - */ -export const result = (predicate: Predicate, okValue: T, failValue: E): IResult => - predicate() - ? ok(okValue) - : fail(failValue) - -/** -* Utility function to quickly create ok/fail pairs, curried variant. -*/ -export const curriedResult = - (predicate: Predicate) => - (okValue: T) => - (failValue: E): IResult => - result(predicate, okValue, failValue) diff --git a/src/reader/pubcli_api.spec.ts b/src/reader/pubcli_api.spec.ts new file mode 100644 index 0000000..e08098d --- /dev/null +++ b/src/reader/pubcli_api.spec.ts @@ -0,0 +1,7 @@ +import { reader, Reader } from './public_api' + +describe('result api', () => { + it('should export', () => { + expect(reader(a => { return 1 })).toBeInstanceOf(Reader) + }) +}) \ No newline at end of file diff --git a/src/reader/public_api.ts b/src/reader/public_api.ts new file mode 100644 index 0000000..2f74e3f --- /dev/null +++ b/src/reader/public_api.ts @@ -0,0 +1,3 @@ +export * from './reader' +export * from './reader.factory' +export * from './reader.interface' \ No newline at end of file diff --git a/src/reader/reader.factory.ts b/src/reader/reader.factory.ts new file mode 100644 index 0000000..ce8eccb --- /dev/null +++ b/src/reader/reader.factory.ts @@ -0,0 +1,5 @@ +import { Reader } from './reader' + +export function reader(fn: (config: TConfig) => TOut) { + return new Reader(fn) +} \ No newline at end of file diff --git a/src/reader/reader.interface.ts b/src/reader/reader.interface.ts new file mode 100644 index 0000000..8d0af2e --- /dev/null +++ b/src/reader/reader.interface.ts @@ -0,0 +1,6 @@ +export interface IReader { + of(fn: (config: E) => A): IReader + run(config: E): A + map(fn: (val: A) => B): IReader + flatMap(fn: (val: A) => IReader): IReader +} diff --git a/test/monads/reader.spec.ts b/src/reader/reader.spec.ts similarity index 68% rename from test/monads/reader.spec.ts rename to src/reader/reader.spec.ts index 65475f3..13a6a51 100644 --- a/test/monads/reader.spec.ts +++ b/src/reader/reader.spec.ts @@ -1,16 +1,16 @@ -import { reader } from '../../src' +import { reader } from './reader.factory' describe('reader', () => { it('should of', () => { - const greet = reader(ctx => ctx + "_HelloA") - const greet2 = greet.of(ctx => ctx + "_HelloB") + const greet = reader(ctx => ctx + '_HelloA') + const greet2 = greet.of(ctx => ctx + '_HelloB') expect(greet.run('Test')).toEqual('Test_HelloA') expect(greet2.run('Test')).toEqual('Test_HelloB') }) it('should map', () => { - const greet = reader(ctx => ctx + "_HelloA") + const greet = reader(ctx => ctx + '_HelloA') const greet2 = greet.map(s => s + '_Mapped123') expect(greet.run('Test')).toEqual('Test_HelloA') @@ -18,10 +18,9 @@ describe('reader', () => { }) it('should flatMap', () => { - - const greet = (name: string) => reader(ctx => ctx + ", " + name) + const greet = (name: string) => reader(ctx => ctx + ', ' + name) const end = (str: string) => reader(a => a === 'Hello') - .flatMap(isH => isH ? reader(ctx => str + "!!!") : reader(ctx => str + ".")) + .flatMap(isH => isH ? reader(ctx => str + '!!!') : reader(ctx => str + '.')) expect(greet('Tom').flatMap(end).run('Hello')).toEqual('Hello, Tom!!!') expect(greet('Jerry').flatMap(end).run('Hi')).toEqual('Hi, Jerry.') diff --git a/src/reader/reader.ts b/src/reader/reader.ts new file mode 100644 index 0000000..be498f2 --- /dev/null +++ b/src/reader/reader.ts @@ -0,0 +1,21 @@ +import { IReader } from './reader.interface' + +export class Reader implements IReader { + constructor(private readonly fn: (config: TConfig) => TOut) { } + + public of(fn: (config: TConfig) => TOut): IReader { + return new Reader(fn) + } + + public flatMap(fn: (val: TOut) => IReader): IReader { + return new Reader(c => fn(this.run(c)).run(c)) + } + + public map(fn: (val: TOut) => TNewOut): IReader { + return new Reader(c => fn(this.run(c))) + } + + public run(config: TConfig): TOut { + return this.fn(config) + } +} diff --git a/src/result/pubcli_api.spec.ts b/src/result/pubcli_api.spec.ts new file mode 100644 index 0000000..977d235 --- /dev/null +++ b/src/result/pubcli_api.spec.ts @@ -0,0 +1,9 @@ +import { Result, fail, ok, result } from './public_api' + +describe('result api', () => { + it('should export', () => { + expect(fail(Error('Test'))).toBeInstanceOf(Result) + expect(ok(1)).toBeInstanceOf(Result) + expect(result(() => true, 1, Error('Test'))).toBeInstanceOf(Result) + }) +}) \ No newline at end of file diff --git a/src/result/public_api.ts b/src/result/public_api.ts new file mode 100644 index 0000000..52ad39e --- /dev/null +++ b/src/result/public_api.ts @@ -0,0 +1,3 @@ +export * from './result' +export * from './result.factory' +export * from './result.interface' \ No newline at end of file diff --git a/src/result/result.factory.ts b/src/result/result.factory.ts new file mode 100644 index 0000000..56e1b7f --- /dev/null +++ b/src/result/result.factory.ts @@ -0,0 +1,17 @@ +import { Result } from './result' +import { IResult, Predicate } from './result.interface' + +export function ok(value: TOk) { + return Result.ok(value) +} + +export function fail(value: TFail) { + return Result.fail(value) +} + +export function result(predicate: Predicate, okValue: TOk, failValue: TFail): IResult { + return predicate() + ? ok(okValue) + : fail(failValue) +} + diff --git a/src/result/result.interface.ts b/src/result/result.interface.ts new file mode 100644 index 0000000..e8ca479 --- /dev/null +++ b/src/result/result.interface.ts @@ -0,0 +1,41 @@ +import { IMaybe } from '../maybe/maybe.interface' + +export type Predicate = () => boolean + +export interface IResultMatchPattern { + readonly ok: (val: T) => U + readonly fail: (val: E) => U +} + +export interface IResult { + isOk(): boolean + isFail(): boolean + maybeOk(): IMaybe + maybeFail(): IMaybe + unwrap(): T | never + unwrapOr(opt: T): T + unwrapFail(): E | never + match(fn: IResultMatchPattern): M + map(fn: (val: T) => M): IResult + mapFail(fn: (err: E) => M): IResult + flatMap(fn: (val: T) => IResult): IResult +} + +export interface IResultOk extends IResult { + unwrap(): T + unwrapOr(opt: T): T + unwrapFail(): never + match(fn: IResultMatchPattern): M + map(fn: (val: T) => M): IResultOk + mapFail(fn: (err: E) => M): IResultOk +} + +export interface IResultFail extends IResult { + unwrap(): never + unwrapOr(opt: T): T + unwrapFail(): E + match(fn: IResultMatchPattern): M + map(fn: (val: T) => M): IResultFail + mapFail(fn: (err: E) => M): IResultFail + flatMap(fn: (val: T) => IResult): IResultFail +} \ No newline at end of file diff --git a/test/monads/result.spec.ts b/src/result/result.spec.ts similarity index 94% rename from test/monads/result.spec.ts rename to src/result/result.spec.ts index 5bccb7f..d9b5bf5 100644 --- a/test/monads/result.spec.ts +++ b/src/result/result.spec.ts @@ -1,4 +1,4 @@ -import { ok, fail, result, curriedResult } from '../../src/monads' +import { ok, fail, result } from './result.factory' describe('result', () => { describe('ok', () => { @@ -155,10 +155,5 @@ describe('result', () => { const sut = result(() => 1 + 1 === 2, true, 'FAILURE!') expect(sut.isOk()).toEqual(true) }) - - it('should return curried', () => { - const sut = curriedResult(() => 1 + 1 === 2)(true)('FAILURE!') - expect(sut.isOk()).toEqual(true) - }) }) }) diff --git a/src/result/result.ts b/src/result/result.ts new file mode 100644 index 0000000..5f94598 --- /dev/null +++ b/src/result/result.ts @@ -0,0 +1,125 @@ +import { IMaybe, maybe, none } from '../maybe/public_api' +import { IResultMatchPattern, IResult } from './result.interface' + +export abstract class Result { + public static ok(value: TOk) { + return new OkResult(value) + } + + public static fail(value: TFail) { + return new FailResult(value) + } + + abstract isOk(): boolean + abstract isFail(): boolean + abstract unwrap(): TOk | never + abstract unwrapOr(opt: TOk): TOk + abstract unwrapFail(): TFail | never + abstract maybeOk(): IMaybe + abstract maybeFail(): IMaybe + abstract match(fn: IResultMatchPattern): M + abstract map(fn: (val: TOk) => M): IResult + abstract mapFail(fn: (err: TFail) => M): IResult + abstract flatMap(fn: (val: TOk) => IResult): IResult +} + +export class OkResult extends Result { + constructor(private readonly successValue: TOk) { + super() + } + + isOk(): boolean { + return true + } + + isFail(): boolean { + return false + } + + unwrap(): TOk { + return this.successValue + } + + unwrapOr(_opt: TOk): TOk { + return this.unwrap() + } + + unwrapFail(): never { + throw new ReferenceError('Cannot unwrap a success as a failure') + } + + maybeOk(): IMaybe { + return maybe(this.successValue) + } + + maybeFail(): IMaybe { + return none() + } + + match(fn: IResultMatchPattern): M { + return fn.ok(this.successValue) + } + + map(fn: (val: TOk) => M): IResult { + return Result.ok(fn(this.successValue)) + } + + mapFail(_: (err: TFail) => M): IResult { + return Result.ok(this.successValue) + } + + flatMap(fn: (val: TOk) => IResult): IResult { + return fn(this.successValue) + } + +} + +export class FailResult extends Result { + constructor(private readonly value: TFail) { + super() + } + + isOk(): boolean { + return false + } + + isFail(): boolean { + return true + } + + unwrap(): TOk { + throw new Error('Cannot unwrap a failure') + } + + unwrapOr(opt: TOk): TOk { + return opt + } + + unwrapFail(): TFail { + return this.value + } + + maybeOk(): IMaybe { + return none() + } + + maybeFail(): IMaybe { + return maybe(this.value) + } + + match(fn: IResultMatchPattern): M { + return fn.fail(this.value) + } + + mapFail(fn: (err: TFail) => M): IResult { + return Result.fail(fn(this.value)) + } + + map(_fn: (val: TOk) => M): IResult { + return Result.fail(this.value) + } + + flatMap(_fn: (val: TOk) => IResult): IResult { + return Result.fail(this.value) + } +} \ No newline at end of file diff --git a/src/util/curry.ts b/src/util/curry.ts deleted file mode 100644 index 1c34029..0000000 --- a/src/util/curry.ts +++ /dev/null @@ -1,50 +0,0 @@ -export const curry2 = - (fn: (arg1: T1, arg2: T2) => TReturn) => - (a1: T1) => - (a2: T2) => - fn(a1, a2) - -export const curry3 = - (fn: (arg1: T1, arg2: T2, arg3: T3) => TReturn) => - (a1: T1) => - (a2: T2) => - (a3: T3) => - fn(a1, a2, a3) - -export const curry4 = - (fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => TReturn) => - (a1: T1) => - (a2: T2) => - (a3: T3) => - (a4: T4) => - fn(a1, a2, a3, a4) - -export const curry5 = - (fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => TReturn) => - (a1: T1) => - (a2: T2) => - (a3: T3) => - (a4: T4) => - (a5: T5) => - fn(a1, a2, a3, a4, a5) - -export const curry6 = - (fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6) => TReturn) => - (a1: T1) => - (a2: T2) => - (a3: T3) => - (a4: T4) => - (a5: T5) => - (a6: T6) => - fn(a1, a2, a3, a4, a5, a6) - -export const curry7 = - (fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7) => TReturn) => - (a1: T1) => - (a2: T2) => - (a3: T3) => - (a4: T4) => - (a5: T5) => - (a6: T6) => - (a7: T7) => - fn(a1, a2, a3, a4, a5, a6, a7) \ No newline at end of file diff --git a/src/util/index.ts b/src/util/index.ts deleted file mode 100644 index 3a101b4..0000000 --- a/src/util/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export { maybeEnv } from './maybe-env' -export { - curry2, - curry3, - curry4, - curry5, - curry6, - curry7 -} from './curry' -export { - maybeToObservable -} from './maybe/maybe-to-observable' diff --git a/src/util/maybe-env.ts b/src/util/maybe-env.ts deleted file mode 100644 index cca4c5a..0000000 --- a/src/util/maybe-env.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { reader, maybe } from '../monads/index' - -export interface GetFromEnvironmentReader { - readEnv(key: string): string | undefined -} - -const DEFAULT_NODE_ENV_READER: GetFromEnvironmentReader = { - readEnv(key: string) { - return process.env[key] - } -} - -const maybeFromEnvReader = (key: string) => (reader: GetFromEnvironmentReader) => maybe(reader.readEnv(key)) - -export function maybeEnv(key: string, config?: GetFromEnvironmentReader) { - return reader(maybeFromEnvReader(key)) - .run(maybe(config).valueOr(DEFAULT_NODE_ENV_READER)) -} diff --git a/test/interfaces/index.spec.ts b/test/interfaces/index.spec.ts deleted file mode 100644 index 2e26f43..0000000 --- a/test/interfaces/index.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import '../../src/interfaces' - -describe('Interfaces', () => { - it('should cover interfaces', () => { - // stubbed just to get coverage up from interfaces - }) -}) \ No newline at end of file diff --git a/test/monads/monad.spec.ts b/test/monads/monad.spec.ts deleted file mode 100644 index 8fb93c3..0000000 --- a/test/monads/monad.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { monad } from '../../src' - -describe('monad', () => { - it('should have required functions', () => { - const mon = monad('string value') - .of('123') - .flatMap(_str => monad('456')) - - expect(mon.flatMap).toBeInstanceOf(Function) - expect(mon.of).toBeInstanceOf(Function) - }) - - it('should have required functions + ...args', () => { - const mon = monad('string value', 1, 2, 3) - .of('123', 1, 2, 3) - .flatMap(_str => monad('456', 1, 2, 3)) - - expect(mon.flatMap).toBeInstanceOf(Function) - expect(mon.of).toBeInstanceOf(Function) - }) -}) \ No newline at end of file diff --git a/test/tsconfig.json b/test/tsconfig.json deleted file mode 100644 index ad35aba..0000000 --- a/test/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", - "lib": ["es2015", "dom"], - "isolatedModules": false, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "noImplicitAny": true, - "noImplicitUseStrict": false, - "noEmitHelpers": false, - "noEmit": true, - "noLib": false, - "noUnusedLocals": true, - "noEmitOnError": true, - "allowSyntheticDefaultImports": false, - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "strict": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "forceConsistentCasingInFileNames": true - } -} \ No newline at end of file diff --git a/test/util/curry.spec.ts b/test/util/curry.spec.ts deleted file mode 100644 index 07a9ab7..0000000 --- a/test/util/curry.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { curry2, curry3, curry4, curry5, curry6, curry7 } from '../../src/util/curry' - -describe('curry', () => { - it('should work with 2 arguments', () => { - const add = (a: number, b: number) => a + b - const add1to = curry2(add)(1) - - expect(add1to(2)).toEqual(3) - }) - - it('should work with 3 arguments', () => { - const add = (a: number, b: number, c: number) => a + b + c - const addCurried = curry3(add) - const add3to = addCurried(1)(2) - - expect(add3to(2)).toEqual(5) - }) - - it('should work with 4 arguments', () => { - const add = (a: number, b: number, c: number, d: number) => a + b + c + d - const addCurried = curry4(add) - const add3to = addCurried(1)(1)(1) - - expect(add3to(2)).toEqual(5) - }) - - it('should work with 5 arguments', () => { - const add = (a: number, b: number, c: number, d: number, e: number) => a + b + c + d + e - const addCurried = curry5(add) - const add4to = addCurried(1)(1)(1)(1) - - expect(add4to(1)).toEqual(5) - }) - - it('should work with 6 arguments', () => { - const add = (a: number, b: number, c: number, d: number, e: number, f: number) => a + b + c + d + e + f - const addCurried = curry6(add) - const add5to = addCurried(1)(1)(1)(1)(1) - - expect(add5to(1)).toEqual(6) - }) - - it('should work with 7 arguments', () => { - const add = (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => a + b + c + d + e + f + g - const addCurried = curry7(add) - const add6to = addCurried(1)(1)(1)(1)(1)(1) - - expect(add6to(1)).toEqual(7) - }) -}) \ No newline at end of file diff --git a/test/util/maybe-env.spec.ts b/test/util/maybe-env.spec.ts deleted file mode 100644 index b1e9a67..0000000 --- a/test/util/maybe-env.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { maybeEnv } from '../../src/util' - -interface StringDict { - readonly [key: string]: string -} - -describe(maybeEnv.name, () => { - const key = 'TEST_JWT' - beforeEach(() => { - remove(key) - }) - - it('should get using default reader', () => { - const hash = 'SOME_HASHED_SECRET' - mutateEnvForTesting(key, hash) - const maybe = maybeEnv(key).valueOr('Fallback') - expect(maybe).toEqual(hash) - }) - - it('should get using custom reader', () => { - const env: StringDict = { - someKey: 'someVal' - } - const maybe = maybeEnv('someKey', { readEnv: (key: string) => env[key] }).valueOr('Fallback') - expect(maybe).toEqual('someVal') - }) - - it('should fallback using custom reader', () => { - const env: StringDict = { - someKey: 'someVal' - } - const maybe = maybeEnv('someUndefinedKey', { readEnv: (key: string) => env[key] }).valueOr('Fallback') - expect(maybe).toEqual('Fallback') - }) - - it('should fallback', () => { - const maybe = maybeEnv(key).valueOr('Fallback') - expect(maybe).toEqual('Fallback') - }) -}) - -// tslint:disable:no-object-mutation -function mutateEnvForTesting(key: string, value: string) { - process.env[key] = value -} - -function remove(key: string) { - // tslint:disable-next-line:no-delete - delete process.env[key] -} \ No newline at end of file diff --git a/tsconfig.build.json b/tsconfig.build.json index 093c853..3ceee2a 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -30,6 +30,6 @@ "sourceMap": true }, "include": [ - "src/**/*.ts" + "src/index.ts" ] } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 99ca95b..be6f130 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,16 +15,16 @@ "noEmitHelpers": false, "noLib": false, "noUnusedLocals": true, + "strictPropertyInitialization": true, "noEmitOnError": true, "allowSyntheticDefaultImports": false, "skipLibCheck": true, "skipDefaultLibCheck": true, "strict": true, "strictNullChecks": true, - "strictFunctionTypes": true, "forceConsistentCasingInFileNames": true, "declaration": true, - "sourceMap": true + "sourceMap": true, }, "include": [ "src/**/*.ts" diff --git a/tslint.json b/tslint.json index 18a8d24..7e98e1c 100644 --- a/tslint.json +++ b/tslint.json @@ -1,19 +1,31 @@ { - "extends": ["tslint-immutable"], + "extends": [ + "tslint-immutable" + ], "rules": { "newline-per-chained-call": false, "prefer-object-spread": true, "no-var-keyword": true, "no-parameter-reassignment": true, "typedef": false, - "indent": [true, "spaces", 2], + "indent": [ + true, + "spaces", + 2 + ], "space-within-parens": false, "object-literal-key-quotes": false, - "semicolon": [true, "never"], + "semicolon": [ + true, + "never" + ], "align": false, "curly": false, "member-ordering": false, - "member-access": [false, "no-public"], + "member-access": [ + false, + "no-public" + ], "newline-before-return": false, "array-type": false, "readonly-keyword": true, @@ -22,10 +34,10 @@ "no-object-mutation": true, "no-delete": true, "no-method-signature": false, - "no-this": true, - "no-class": true, "no-expression-statement": false, - "no-if-statement": true, - "quotemark": [true, "single"] + "quotemark": [ + true, + "single" + ] } } \ No newline at end of file