From 3d4004719a98665e49627a63edd4d4a2a72aa155 Mon Sep 17 00:00:00 2001 From: Chris Wilkinson Date: Fri, 14 Jan 2022 09:39:24 +0000 Subject: [PATCH 1/9] Allow imports without the lib or es6 directories --- .gitignore | 3 +- README.md | 20 ++++---- docs/index.md | 20 ++++---- examples/decodeParam.ts | 2 +- examples/error-handling.ts | 2 +- examples/json-middleware.ts | 2 +- package-lock.json | 97 +++++++++++++++++++++++++++++++------ package.json | 24 ++++----- scripts/FileSystem.ts | 29 +++++++++++ scripts/build.ts | 88 +++++++++++++++++++++++++++++++++ scripts/pre-publish.ts | 6 +++ scripts/release.ts | 25 ++++++++++ scripts/run.ts | 21 ++++++++ test/express.ts | 4 +- tsconfig.build-es6.json | 3 +- tsconfig.json | 3 +- 16 files changed, 294 insertions(+), 55 deletions(-) create mode 100644 scripts/FileSystem.ts create mode 100644 scripts/build.ts create mode 100644 scripts/pre-publish.ts create mode 100644 scripts/release.ts create mode 100644 scripts/run.ts diff --git a/.gitignore b/.gitignore index 556d043..6f135c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ *.log node_modules -lib -es6 dev coverage +dist diff --git a/README.md b/README.md index ea7aff6..ea90128 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ cannot be made. A few examples of such mistakes could be: ```ts import * as express from 'express' import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' -import { toRequestHandler } from 'hyper-ts/lib/express' +import * as M from 'hyper-ts/Middleware' +import { toRequestHandler } from 'hyper-ts/express' import { pipe } from 'fp-ts/function' const hello: M.Middleware = pipe( @@ -103,7 +103,7 @@ During the connection lifecycle the following flow is statically enforced StatusOpen -> HeadersOpen -> BodyOpen -> ResponseEnded ``` -**Note**. `hyper-ts` supports [express 4.x](http://expressjs.com/) by default by exporting a `Connection` instance from the `hyper-ts/lib/express` module. +**Note**. `hyper-ts` supports [express 4.x](http://expressjs.com/) by default by exporting a `Connection` instance from the `hyper-ts/express` module. ## Middleware @@ -152,7 +152,7 @@ Input validation/decoding is done by defining a decoding function with the follo ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as E from 'fp-ts/Either' const isUnknownRecord = (u: unknown): u is Record => typeof u === 'object' && u !== null @@ -167,7 +167,7 @@ You can also use [io-ts](https://github.com/gcanti/io-ts) decoders. ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as t from 'io-ts' // returns a middleware validating `req.param.user_id` @@ -181,8 +181,8 @@ Here I'm using `t.string` but you can pass _any_ `io-ts` runtime type ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' -import { IntFromString } from 'io-ts-types/lib/IntFromString' +import * as M from 'hyper-ts/Middleware' +import { IntFromString } from 'io-ts-types/IntFromString' // validation succeeds only if `req.param.user_id` can be parsed to an integer export const middleware3: M.Middleware< @@ -197,7 +197,7 @@ export const middleware3: M.Middleware< ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as t from 'io-ts' // returns a middleware validating both `req.param.user_id` and `req.param.user_name` @@ -213,7 +213,7 @@ export const middleware = M.decodeParams( ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as t from 'io-ts' // return a middleware validating the query "order=desc&shoe[color]=blue&shoe[type]=converse" @@ -232,7 +232,7 @@ export const middleware = M.decodeQuery( ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as t from 'io-ts' // return a middleware validating `req.body` diff --git a/docs/index.md b/docs/index.md index 0884525..6e909a5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -38,8 +38,8 @@ cannot be made. A few examples of such mistakes could be: ```ts import * as express from 'express' import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' -import { toRequestHandler } from 'hyper-ts/lib/express' +import * as M from 'hyper-ts/Middleware' +import { toRequestHandler } from 'hyper-ts/express' import { pipe } from 'fp-ts/function' const hello: M.Middleware = pipe( @@ -108,7 +108,7 @@ During the connection lifecycle the following flow is statically enforced StatusOpen -> HeadersOpen -> BodyOpen -> ResponseEnded ``` -**Note**. `hyper-ts` supports [express 4.x](http://expressjs.com/) by default by exporting a `Connection` instance from the `hyper-ts/lib/express` module. +**Note**. `hyper-ts` supports [express 4.x](http://expressjs.com/) by default by exporting a `Connection` instance from the `hyper-ts/express` module. ## Middleware @@ -157,7 +157,7 @@ Input validation/decoding is done by defining a decoding function with the follo ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as E from 'fp-ts/Either' const isUnknownRecord = (u: unknown): u is Record => typeof u === 'object' && u !== null @@ -172,7 +172,7 @@ You can also use [io-ts](https://github.com/gcanti/io-ts) decoders. ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as t from 'io-ts' // returns a middleware validating `req.param.user_id` @@ -186,8 +186,8 @@ Here I'm using `t.string` but you can pass _any_ `io-ts` runtime type ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' -import { IntFromString } from 'io-ts-types/lib/IntFromString' +import * as M from 'hyper-ts/Middleware' +import { IntFromString } from 'io-ts-types/IntFromString' // validation succeeds only if `req.param.user_id` can be parsed to an integer export const middleware3: M.Middleware< @@ -202,7 +202,7 @@ export const middleware3: M.Middleware< ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as t from 'io-ts' // returns a middleware validating both `req.param.user_id` and `req.param.user_name` @@ -218,7 +218,7 @@ export const middleware = M.decodeParams( ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as t from 'io-ts' // return a middleware validating the query "order=desc&shoe[color]=blue&shoe[type]=converse" @@ -237,7 +237,7 @@ export const middleware = M.decodeQuery( ```ts import * as H from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware' +import * as M from 'hyper-ts/Middleware' import * as t from 'io-ts' // return a middleware validating `req.body` diff --git a/examples/decodeParam.ts b/examples/decodeParam.ts index 3c99c8d..0587a74 100644 --- a/examples/decodeParam.ts +++ b/examples/decodeParam.ts @@ -16,7 +16,7 @@ export const middleware2: M.Middleware TE.TaskEither + readonly writeFile: (path: string, content: string) => TE.TaskEither + readonly copyFile: (from: string, to: string) => TE.TaskEither + readonly glob: (pattern: string) => TE.TaskEither> + readonly mkdir: (path: string) => TE.TaskEither +} + +const readFile = TE.taskify(fs.readFile) +const writeFile = TE.taskify(fs.writeFile) +const copyFile = TE.taskify(fs.copyFile) +const glob = TE.taskify>(G) +const mkdirTE = TE.taskify(fs.mkdir) + +export const fileSystem: FileSystem = { + readFile: (path) => readFile(path, 'utf8'), + writeFile, + copyFile, + glob, + mkdir: flow( + mkdirTE, + TE.map(() => undefined) + ), +} diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..926644c --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,88 @@ +import * as path from 'path' +import * as E from 'fp-ts/Either' +import { pipe } from 'fp-ts/function' +import * as RTE from 'fp-ts/ReaderTaskEither' +import * as A from 'fp-ts/ReadonlyArray' +import * as TE from 'fp-ts/TaskEither' +import { FileSystem, fileSystem } from './FileSystem' +import { run } from './run' + +interface Build extends RTE.ReaderTaskEither {} + +const OUTPUT_FOLDER = 'dist' +const PKG = 'package.json' + +export const copyPackageJson: Build = (C) => + pipe( + C.readFile(PKG), + TE.chain((s) => TE.fromEither(E.parseJSON(s, E.toError))), + TE.map((v) => { + const clone = Object.assign({}, v as any) + + delete clone.scripts + delete clone.files + delete clone.devDependencies + + return clone + }), + TE.chain((json) => C.writeFile(path.join(OUTPUT_FOLDER, PKG), JSON.stringify(json, null, 2))) + ) + +export const FILES: ReadonlyArray = ['CHANGELOG.md', 'LICENSE', 'README.md'] + +export const copyFiles: Build> = (C) => + pipe( + FILES, + A.traverse(TE.taskEither)((from) => C.copyFile(from, path.resolve(OUTPUT_FOLDER, from))) + ) + +const traverse = A.traverse(TE.taskEither) + +export const makeModules: Build = (C) => + pipe( + C.glob(`${OUTPUT_FOLDER}/lib/*.js`), + TE.map(getModules), + TE.chain(traverse(makeSingleModule(C))), + TE.map(() => undefined) + ) + +function getModules(paths: ReadonlyArray): ReadonlyArray { + return paths.map((filePath) => path.basename(filePath, '.js')).filter((x) => x !== 'index') +} + +function makeSingleModule(C: FileSystem): (module: string) => TE.TaskEither { + return (m) => + pipe( + C.mkdir(path.join(OUTPUT_FOLDER, m)), + TE.chain(() => makePkgJson(m)), + TE.chain((data) => C.writeFile(path.join(OUTPUT_FOLDER, m, 'package.json'), data)) + ) +} + +function makePkgJson(module: string): TE.TaskEither { + return pipe( + JSON.stringify( + { + main: `../lib/${module}.js`, + module: `../es6/${module}.js`, + typings: `../lib/${module}.d.ts`, + sideEffects: false, + }, + null, + 2 + ), + TE.right + ) +} + +const main: Build = pipe( + copyPackageJson, + RTE.chain(() => copyFiles), + RTE.chain(() => makeModules) +) + +run( + main({ + ...fileSystem, + }) +) diff --git a/scripts/pre-publish.ts b/scripts/pre-publish.ts new file mode 100644 index 0000000..53dd325 --- /dev/null +++ b/scripts/pre-publish.ts @@ -0,0 +1,6 @@ +import { left } from 'fp-ts/TaskEither' +import { run } from './run' + +const main = left(new Error('"npm publish" can not be run from root, run "npm run release" instead')) + +run(main) diff --git a/scripts/release.ts b/scripts/release.ts new file mode 100644 index 0000000..1d78c26 --- /dev/null +++ b/scripts/release.ts @@ -0,0 +1,25 @@ +import * as child_process from 'child_process' +import { left, right } from 'fp-ts/Either' +import * as TE from 'fp-ts/TaskEither' +import { run } from './run' + +const DIST = 'dist' + +const exec = + (cmd: string, args?: child_process.ExecOptions): TE.TaskEither => + () => + new Promise((resolve) => { + child_process.exec(cmd, args, (err) => { + if (err !== null) { + return resolve(left(err)) + } + + return resolve(right(undefined)) + }) + }) + +export const main = exec('npm publish', { + cwd: DIST, +}) + +run(main) diff --git a/scripts/run.ts b/scripts/run.ts new file mode 100644 index 0000000..7629fe1 --- /dev/null +++ b/scripts/run.ts @@ -0,0 +1,21 @@ +import { fold } from 'fp-ts/Either' +import { TaskEither } from 'fp-ts/TaskEither' + +export function run(eff: TaskEither): void { + eff() + .then( + fold( + (e) => { + throw e + }, + (_) => { + process.exitCode = 0 + } + ) + ) + .catch((e) => { + console.error(e) // tslint:disable-line no-console + + process.exitCode = 1 + }) +} diff --git a/test/express.ts b/test/express.ts index 535439f..bb6e7b2 100644 --- a/test/express.ts +++ b/test/express.ts @@ -3,8 +3,8 @@ import * as M from '../src/Middleware' import { fromRequestHandler, toRequestHandler } from '../src/express' import { flow, pipe } from 'fp-ts/function' import { Readable } from 'stream' -import * as express from 'express' -import * as supertest from 'supertest' +import express from 'express' +import supertest from 'supertest' import * as t from 'io-ts' import * as E from 'fp-ts/Either' diff --git a/tsconfig.build-es6.json b/tsconfig.build-es6.json index 743cc30..5f245be 100644 --- a/tsconfig.build-es6.json +++ b/tsconfig.build-es6.json @@ -1,7 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "outDir": "./es6", + "outDir": "dist/es6", + "target": "es6", "module": "es6" } } diff --git a/tsconfig.json b/tsconfig.json index b309c6b..c95fe4c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,9 @@ { "compilerOptions": { "noEmit": true, - "outDir": "./lib", + "outDir": "dist/lib", "target": "es5", + "esModuleInterop": true, "module": "commonjs", "moduleResolution": "node", "lib": ["es6"], From f430fc404e75e78dcf89021c4a30d383a60e8b42 Mon Sep 17 00:00:00 2001 From: Denis Frezzato Date: Sat, 30 Apr 2022 09:55:25 +0200 Subject: [PATCH 2/9] Add chainMiddlewareKW --- docs/modules/ReaderMiddleware.ts.md | 19 +++++++++++++++++-- dtslint/ts3.5/ReaderMiddleware.ts | 16 ++++++++++++++++ src/ReaderMiddleware.ts | 20 +++++++++++++++----- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/docs/modules/ReaderMiddleware.ts.md b/docs/modules/ReaderMiddleware.ts.md index 6339abc..0c2fa7e 100644 --- a/docs/modules/ReaderMiddleware.ts.md +++ b/docs/modules/ReaderMiddleware.ts.md @@ -60,6 +60,7 @@ Added in v0.6.3 - [chainFirstW](#chainfirstw) - [chainIOK](#chainiok) - [chainMiddlewareK](#chainmiddlewarek) + - [chainMiddlewareKW](#chainmiddlewarekw) - [chainOptionK](#chainoptionk) - [chainOptionKW](#chainoptionkw) - [chainReaderTaskEitherK](#chainreadertaskeitherk) @@ -691,13 +692,27 @@ Added in v0.7.0 **Signature** ```ts -export declare const chainMiddlewareK: ( +export declare const chainMiddlewareK: ( f: (a: A) => M.Middleware -) => (ma: ReaderMiddleware) => ReaderMiddleware +) => (ma: ReaderMiddleware) => ReaderMiddleware ``` Added in v0.6.3 +## chainMiddlewareKW + +Less strict version of [`chainMiddlewareK`](#chainmiddlewarek). + +**Signature** + +```ts +export declare const chainMiddlewareKW: ( + f: (a: A) => M.Middleware +) => (ma: ReaderMiddleware) => ReaderMiddleware +``` + +Added in v0.7.9 + ## chainOptionK **Signature** diff --git a/dtslint/ts3.5/ReaderMiddleware.ts b/dtslint/ts3.5/ReaderMiddleware.ts index 3c66e45..8977cb4 100644 --- a/dtslint/ts3.5/ReaderMiddleware.ts +++ b/dtslint/ts3.5/ReaderMiddleware.ts @@ -430,3 +430,19 @@ pipe( middleware1, _.chainFirstTaskOptionKW(() => true)((_: boolean) => TO.some(2)) ) + +// +// chainMiddlewareK +// + +// $ExpectType ReaderMiddleware +pipe( + middleware1, + _.chainMiddlewareK(() => middleware4a) +) + +// $ExpectType ReaderMiddleware +pipe( + middleware1, + _.chainMiddlewareKW(() => middleware4b) +) diff --git a/src/ReaderMiddleware.ts b/src/ReaderMiddleware.ts index 6551b77..b2c7b30 100644 --- a/src/ReaderMiddleware.ts +++ b/src/ReaderMiddleware.ts @@ -749,17 +749,27 @@ export const altW: ( ) => (fa: ReaderMiddleware) => ReaderMiddleware = alt as any /** + * Less strict version of [`chainMiddlewareK`](#chainmiddlewarek). + * * @category combinators - * @since 0.6.3 + * @since 0.7.9 */ -export const chainMiddlewareK = - (f: (a: A) => M.Middleware) => - (ma: ReaderMiddleware): ReaderMiddleware => +export const chainMiddlewareKW = + (f: (a: A) => M.Middleware) => + (ma: ReaderMiddleware): ReaderMiddleware => pipe( ma, - chain((a) => fromMiddleware(f(a))) + chainW((a) => fromMiddleware(f(a))) ) +/** + * @category combinators + * @since 0.6.3 + */ +export const chainMiddlewareK: ( + f: (a: A) => M.Middleware +) => (ma: ReaderMiddleware) => ReaderMiddleware = chainMiddlewareKW + /** * @category combinators * @since 0.6.3 From 7552db984916c193365321b395ae1edff6c8a503 Mon Sep 17 00:00:00 2001 From: Chris Wilkinson Date: Thu, 21 Jul 2022 14:18:59 +0100 Subject: [PATCH 3/9] Add fromReaderK --- docs/modules/ReaderMiddleware.ts.md | 13 +++++++++++++ dtslint/ts3.5/ReaderMiddleware.ts | 10 ++++++++++ src/ReaderMiddleware.ts | 11 +++++++++++ test/ReaderMiddleware.ts | 8 ++++++++ 4 files changed, 42 insertions(+) diff --git a/docs/modules/ReaderMiddleware.ts.md b/docs/modules/ReaderMiddleware.ts.md index 6339abc..499e9ab 100644 --- a/docs/modules/ReaderMiddleware.ts.md +++ b/docs/modules/ReaderMiddleware.ts.md @@ -76,6 +76,7 @@ Added in v0.6.3 - [flatten](#flatten) - [flattenW](#flattenw) - [fromIOK](#fromiok) + - [fromReaderK](#fromreaderk) - [fromReaderTaskEitherK](#fromreadertaskeitherk) - [fromReaderTaskK](#fromreadertaskk) - [fromTaskK](#fromtaskk) @@ -916,6 +917,18 @@ export declare const fromIOK: (f: (...a: A) => IO) => (...a: A Added in v0.7.0 +## fromReaderK + +**Signature** + +```ts +export declare const fromReaderK: ( + f: (...a: A) => Reader +) => (...a: A) => ReaderMiddleware +``` + +Added in v0.7.9 + ## fromReaderTaskEitherK **Signature** diff --git a/dtslint/ts3.5/ReaderMiddleware.ts b/dtslint/ts3.5/ReaderMiddleware.ts index 3c66e45..fab694c 100644 --- a/dtslint/ts3.5/ReaderMiddleware.ts +++ b/dtslint/ts3.5/ReaderMiddleware.ts @@ -1,6 +1,7 @@ import * as E from 'fp-ts/Either' import * as O from 'fp-ts/Option' import { pipe } from 'fp-ts/function' +import { Reader } from 'fp-ts/Reader' import { ReaderTask } from 'fp-ts/ReaderTask' import { ReaderTaskEither } from 'fp-ts/ReaderTaskEither' import * as TO from 'fp-ts/TaskOption' @@ -24,6 +25,8 @@ declare const middleware4a: M.Middleware<'one', 'one', number, boolean> declare const middleware4b: M.Middleware<'one', 'one', Error, string> declare const middleware5: M.Middleware<'one', 'two', number, string> +declare const reader1: Reader + declare const readerTask1: ReaderTask declare const readerTask2: ReaderTask @@ -50,6 +53,13 @@ _.asksReaderMiddleware((r: R1) => _.of(true)) // $ExpectError _.asksReaderMiddleware((r: R1) => _.of(true)) +// +// fromReaderK +// + +// $ExpectType (a: boolean, b: number) => ReaderMiddleware +_.fromReaderK((a: boolean, b: number) => reader1) + // // fromReaderTaskK // diff --git a/src/ReaderMiddleware.ts b/src/ReaderMiddleware.ts index 6551b77..fe982ff 100644 --- a/src/ReaderMiddleware.ts +++ b/src/ReaderMiddleware.ts @@ -269,6 +269,17 @@ export const asksReaderMiddleware: ( f: (r: R) => ReaderMiddleware ) => ReaderMiddleware = asksReaderMiddlewareW +/** + * @category combinators + * @since 0.7.9 + */ +export const fromReaderK = + , B, I = H.StatusOpen, E = never>( + f: (...a: A) => Reader + ): ((...a: A) => ReaderMiddleware) => + (...a) => + rightReader(f(...a)) + /** * @category combinators * @since 0.7.8 diff --git a/test/ReaderMiddleware.ts b/test/ReaderMiddleware.ts index 0b2d4fd..995db59 100644 --- a/test/ReaderMiddleware.ts +++ b/test/ReaderMiddleware.ts @@ -2,6 +2,7 @@ import * as assert from 'assert' import * as E from 'fp-ts/Either' import * as O from 'fp-ts/Option' import * as TE from 'fp-ts/TaskEither' +import * as R from 'fp-ts/Reader' import * as RT from 'fp-ts/ReaderTask' import * as RTE from 'fp-ts/ReaderTaskEither' import * as TO from 'fp-ts/TaskOption' @@ -86,6 +87,13 @@ describe('ReaderMiddleware', () => { return assertProperty(m1, undefined, m2, c) }) + it('fromReaderK', () => { + const m2 = (value: string) => R.of(value.length) + const m1 = _.fromReaderK(m2) + const c = new MockConnection(new MockRequest()) + return assertSuccess(m1('foo'), undefined, c, 3, []) + }) + it('fromReaderTaskK', () => { const m2 = (value: string) => RT.of(value.length) const m1 = _.fromReaderTaskK(m2) From a5deee7d9842a729b9a0638747093706fd35bd48 Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Mon, 25 Jul 2022 16:06:44 +0200 Subject: [PATCH 4/9] indexed version of ap, apFirst and apSecond added. Closed #84 --- docs/modules/Middleware.ts.md | 90 +++++++++++++++++++++++++++++ docs/modules/ReaderMiddleware.ts.md | 90 +++++++++++++++++++++++++++++ src/Middleware.ts | 60 +++++++++++++++++++ src/ReaderMiddleware.ts | 65 +++++++++++++++++++++ 4 files changed, 305 insertions(+) diff --git a/docs/modules/Middleware.ts.md b/docs/modules/Middleware.ts.md index b1462bc..06ec3ab 100644 --- a/docs/modules/Middleware.ts.md +++ b/docs/modules/Middleware.ts.md @@ -22,6 +22,8 @@ Added in v0.7.0 - [Apply](#apply) - [ap](#ap) - [apW](#apw) + - [iap](#iap) + - [iapW](#iapw) - [Bifunctor](#bifunctor) - [bimap](#bimap) - [mapLeft](#mapleft) @@ -68,6 +70,10 @@ Added in v0.7.0 - [flattenW](#flattenw) - [fromIOK](#fromiok) - [fromTaskK](#fromtaskk) + - [iapFirst](#iapfirst) + - [iapFirstW](#iapfirstw) + - [iapSecond](#iapsecond) + - [iapSecondW](#iapsecondw) - [ichainFirst](#ichainfirst) - [ichainFirstW](#ichainfirstw) - [iflatten](#iflatten) @@ -207,6 +213,34 @@ export declare const apW: ( Added in v0.7.0 +## iap + +Indexed version of [`ap`](#ap). + +**Signature** + +```ts +export declare const iap: ( + fa: Middleware +) => (fab: Middleware B>) => Middleware +``` + +Added in v0.7.9 + +## iapW + +Less strict version of [`iap`](#iap). + +**Signature** + +```ts +export declare const iapW: ( + fa: Middleware +) => (fab: Middleware B>) => Middleware +``` + +Added in v0.7.9 + # Bifunctor ## bimap @@ -720,6 +754,62 @@ export declare const fromTaskK: (f: (...a: A) => T.Task) => (...a Added in v0.7.0 +## iapFirst + +Indexed version of [`apFirst`](#apfirst). + +**Signature** + +```ts +export declare const iapFirst: ( + second: Middleware +) => (first: Middleware) => Middleware +``` + +Added in v0.7.9 + +## iapFirstW + +Less strict version of [`iapFirst`](#iapfirst). + +**Signature** + +```ts +export declare const iapFirstW: ( + second: Middleware +) => (first: Middleware) => Middleware +``` + +Added in v0.7.1 + +## iapSecond + +Indexed version of [`apSecond`](#apsecond). + +**Signature** + +```ts +export declare const iapSecond: ( + second: Middleware +) => (first: Middleware) => Middleware +``` + +Added in v0.7.9 + +## iapSecondW + +Less strict version of [`iapSecond`](#iapsecond). + +**Signature** + +```ts +export declare const iapSecondW: ( + second: Middleware +) => (first: Middleware) => Middleware +``` + +Added in v0.7.9 + ## ichainFirst Indexed version of [`chainFirst`](#chainfirst). diff --git a/docs/modules/ReaderMiddleware.ts.md b/docs/modules/ReaderMiddleware.ts.md index 6339abc..b1d64b9 100644 --- a/docs/modules/ReaderMiddleware.ts.md +++ b/docs/modules/ReaderMiddleware.ts.md @@ -18,6 +18,8 @@ Added in v0.6.3 - [Apply](#apply) - [ap](#ap) - [apW](#apw) + - [iap](#iap) + - [iapW](#iapw) - [Bifunctor](#bifunctor) - [bimap](#bimap) - [mapLeft](#mapleft) @@ -79,6 +81,10 @@ Added in v0.6.3 - [fromReaderTaskEitherK](#fromreadertaskeitherk) - [fromReaderTaskK](#fromreadertaskk) - [fromTaskK](#fromtaskk) + - [iapFirst](#iapfirst) + - [iapFirstW](#iapfirstw) + - [iapSecond](#iapsecond) + - [iapSecondW](#iapsecondw) - [ichainMiddlewareK](#ichainmiddlewarek) - [ichainMiddlewareKW](#ichainmiddlewarekw) - [iflatten](#iflatten) @@ -223,6 +229,34 @@ export declare const apW: ( Added in v0.6.3 +## iap + +Indexed version of [`ap`](#ap). + +**Signature** + +```ts +export declare const iap: ( + fa: ReaderMiddleware +) => (fab: ReaderMiddleware B>) => ReaderMiddleware +``` + +Added in v0.7.9 + +## iapW + +Less strict version of [`iap`](#iap). + +**Signature** + +```ts +export declare const iapW: ( + fa: ReaderMiddleware +) => (fab: ReaderMiddleware B>) => ReaderMiddleware +``` + +Added in v0.6.3 + # Bifunctor ## bimap @@ -950,6 +984,62 @@ export declare const fromTaskK: (f: (...a: A) => Task) => (... Added in v0.7.0 +## iapFirst + +Indexed version of [`apFirst`](#apfirst). + +**Signature** + +```ts +export declare const iapFirst: ( + second: ReaderMiddleware +) => (first: ReaderMiddleware) => ReaderMiddleware +``` + +Added in v0.7.9 + +## iapFirstW + +Less strict version of [`iapFirst`](#iapfirst). + +**Signature** + +```ts +export declare const iapFirstW: ( + second: ReaderMiddleware +) => (first: ReaderMiddleware) => ReaderMiddleware +``` + +Added in v0.7.9 + +## iapSecond + +Indexed version of [`apSecond`](#apsecond). + +**Signature** + +```ts +export declare const iapSecond: ( + second: ReaderMiddleware +) => (first: ReaderMiddleware) => ReaderMiddleware +``` + +Added in v0.7.9 + +## iapSecondW + +Less strict version of [`iapSecond`](#iapsecond). + +**Signature** + +```ts +export declare const iapSecondW: ( + second: ReaderMiddleware +) => (first: ReaderMiddleware) => ReaderMiddleware +``` + +Added in v0.7.9 + ## ichainMiddlewareK **Signature** diff --git a/src/Middleware.ts b/src/Middleware.ts index b4a3d07..9eeacd8 100644 --- a/src/Middleware.ts +++ b/src/Middleware.ts @@ -202,6 +202,26 @@ export const apW: ( fa: Middleware ) => (fab: Middleware B>) => Middleware = ap as any +/** + * Indexed version of [`ap`](#ap). + * + * @category Apply + * @since 0.7.9 + */ +export const iap: ( + fa: Middleware +) => (fab: Middleware B>) => Middleware = ap as any + +/** + * Less strict version of [`iap`](#iap). + * + * @category Apply + * @since 0.7.9 + */ +export const iapW: ( + fa: Middleware +) => (fab: Middleware B>) => Middleware = iap as any + /** * @category Pointed * @since 0.7.0 @@ -848,6 +868,26 @@ export const apFirstW: ( second: Middleware ) => (first: Middleware) => Middleware = apFirst as any +/** + * Indexed version of [`apFirst`](#apfirst). + * + * @category combinators + * @since 0.7.9 + */ +export const iapFirst: ( + second: Middleware +) => (first: Middleware) => Middleware = apFirst as any + +/** + * Less strict version of [`iapFirst`](#iapfirst). + * + * @category combinators + * @since 0.7.1 + */ +export const iapFirstW: ( + second: Middleware +) => (first: Middleware) => Middleware = iapFirst as any + /** * @category combinators * @since 0.7.0 @@ -864,6 +904,26 @@ export const apSecondW: ( second: Middleware ) => (first: Middleware) => Middleware = apSecond as any +/** + * Indexed version of [`apSecond`](#apsecond). + * + * @category combinators + * @since 0.7.9 + */ +export const iapSecond: ( + second: Middleware +) => (first: Middleware) => Middleware = apSecond as any + +/** + * Less strict version of [`iapSecond`](#iapsecond). + * + * @category combinators + * @since 0.7.9 + */ +export const iapSecondW: ( + second: Middleware +) => (first: Middleware) => Middleware = iapSecond as any + /** * Composes computations in sequence, using the return value of one computation to determine the next computation and * keeping only the result of the first. diff --git a/src/ReaderMiddleware.ts b/src/ReaderMiddleware.ts index 6551b77..9e75df0 100644 --- a/src/ReaderMiddleware.ts +++ b/src/ReaderMiddleware.ts @@ -600,6 +600,29 @@ export const apW: ( ) => (fab: ReaderMiddleware B>) => ReaderMiddleware = ap as any +/** + * Indexed version of [`ap`](#ap). + * + * @category Apply + * @since 0.7.9 + */ +export const iap = + (fa: ReaderMiddleware) => + (fab: ReaderMiddleware B>): ReaderMiddleware => + (r) => + pipe(fab(r), M.iap(fa(r))) + +/** + * Less strict version of [`iap`](#iap). + * + * @category Apply + * @since 0.6.3 + */ +export const iapW: ( + fa: ReaderMiddleware +) => (fab: ReaderMiddleware B>) => ReaderMiddleware = + iap as any + /** * @category Pointed * @since 0.6.3 @@ -1028,6 +1051,27 @@ export const apFirstW: ( ) => (first: ReaderMiddleware) => ReaderMiddleware = apFirst as any +/** + * Indexed version of [`apFirst`](#apfirst). + * + * @category combinators + * @since 0.7.9 + */ +export const iapFirst: ( + second: ReaderMiddleware +) => (first: ReaderMiddleware) => ReaderMiddleware = apFirst as any + +/** + * Less strict version of [`iapFirst`](#iapfirst). + * + * @category combinators + * @since 0.7.9 + */ +export const iapFirstW: ( + second: ReaderMiddleware +) => (first: ReaderMiddleware) => ReaderMiddleware = + iapFirst as any + /** * @category combinators * @since 0.7.0 @@ -1045,6 +1089,27 @@ export const apSecondW: ( ) => (first: ReaderMiddleware) => ReaderMiddleware = apSecond as any +/** + * Indexed version of [`apSecond`](#apsecond). + * + * @category combinators + * @since 0.7.9 + */ +export const iapSecond: ( + second: ReaderMiddleware +) => (first: ReaderMiddleware) => ReaderMiddleware = apSecond as any + +/** + * Less strict version of [`iapSecond`](#iapsecond). + * + * @category combinators + * @since 0.7.9 + */ +export const iapSecondW: ( + second: ReaderMiddleware +) => (first: ReaderMiddleware) => ReaderMiddleware = + iapSecond as any + /** * Composes computations in sequence, using the return value of one computation to determine * the next computation and keeping only the result of the first. From a382599ef4869f60007983b9afaeac79bddef15f Mon Sep 17 00:00:00 2001 From: Chris Wilkinson Date: Fri, 30 Sep 2022 10:55:58 +0100 Subject: [PATCH 5/9] Add chainReaderK --- docs/modules/ReaderMiddleware.ts.md | 28 +++++++++++++++++++++++++ dtslint/ts3.5/ReaderMiddleware.ts | 32 +++++++++++++++++++++++++++++ src/ReaderMiddleware.ts | 19 +++++++++++++++++ test/ReaderMiddleware.ts | 20 ++++++++++++++++++ 4 files changed, 99 insertions(+) diff --git a/docs/modules/ReaderMiddleware.ts.md b/docs/modules/ReaderMiddleware.ts.md index 499e9ab..71c9a1f 100644 --- a/docs/modules/ReaderMiddleware.ts.md +++ b/docs/modules/ReaderMiddleware.ts.md @@ -62,6 +62,8 @@ Added in v0.6.3 - [chainMiddlewareK](#chainmiddlewarek) - [chainOptionK](#chainoptionk) - [chainOptionKW](#chainoptionkw) + - [chainReaderK](#chainreaderk) + - [chainReaderKW](#chainreaderkw) - [chainReaderTaskEitherK](#chainreadertaskeitherk) - [chainReaderTaskEitherKW](#chainreadertaskeitherkw) - [chainReaderTaskK](#chainreadertaskk) @@ -727,6 +729,32 @@ export declare const chainOptionKW: ( Added in v0.7.9 +## chainReaderK + +**Signature** + +```ts +export declare const chainReaderK: ( + f: (a: A) => Reader +) => (ma: ReaderMiddleware) => ReaderMiddleware +``` + +Added in v0.7.9 + +## chainReaderKW + +Less strict version of [`chainReaderK`](#chainreaderk). + +**Signature** + +```ts +export declare const chainReaderKW: ( + f: (a: A) => Reader +) => (ma: ReaderMiddleware) => ReaderMiddleware +``` + +Added in v0.7.9 + ## chainReaderTaskEitherK **Signature** diff --git a/dtslint/ts3.5/ReaderMiddleware.ts b/dtslint/ts3.5/ReaderMiddleware.ts index fab694c..f3152e5 100644 --- a/dtslint/ts3.5/ReaderMiddleware.ts +++ b/dtslint/ts3.5/ReaderMiddleware.ts @@ -26,6 +26,7 @@ declare const middleware4b: M.Middleware<'one', 'one', Error, string> declare const middleware5: M.Middleware<'one', 'two', number, string> declare const reader1: Reader +declare const reader2: Reader declare const readerTask1: ReaderTask declare const readerTask2: ReaderTask @@ -255,6 +256,37 @@ pipe( _.chainTaskOptionKW(() => true)((_: boolean) => TO.some(2)) ) +// +// chainReaderKW +// + +// $ExpectType ReaderMiddleware +pipe( + middleware1, + _.chainReaderKW(() => reader1) +) + +// $ExpectType ReaderMiddleware +pipe( + middleware1, + _.chainReaderKW(() => reader2) +) + +// +// chainReaderK +// + +// $ExpectType ReaderMiddleware +pipe( + middleware1, + _.chainReaderK(() => reader1) +) + +pipe( + middleware1, + _.chainReaderK(() => reader2) // $ExpectError +) + // // chainReaderTaskKW // diff --git a/src/ReaderMiddleware.ts b/src/ReaderMiddleware.ts index fe982ff..35ce918 100644 --- a/src/ReaderMiddleware.ts +++ b/src/ReaderMiddleware.ts @@ -830,6 +830,25 @@ export const chainTaskOptionK: ( f: (a: A) => TO.TaskOption ) => (ma: ReaderMiddleware) => ReaderMiddleware = chainTaskOptionKW +/** + * Less strict version of [`chainReaderK`](#chainreaderk). + * + * @category combinators + * @since 0.7.9 + */ +export const chainReaderKW: ( + f: (a: A) => Reader +) => (ma: ReaderMiddleware) => ReaderMiddleware = (f) => + chainW(fromReaderK(f)) + +/** + * @category combinators + * @since 0.7.9 + */ +export const chainReaderK: ( + f: (a: A) => Reader +) => (ma: ReaderMiddleware) => ReaderMiddleware = chainReaderKW + /** * Less strict version of [`chainReaderTaskK`](#chainreadertaskk). * diff --git a/test/ReaderMiddleware.ts b/test/ReaderMiddleware.ts index 995db59..fb8fd92 100644 --- a/test/ReaderMiddleware.ts +++ b/test/ReaderMiddleware.ts @@ -525,6 +525,26 @@ describe('ReaderMiddleware', () => { }) }) + it('chainReaderKW', () => { + const m1 = pipe( + _.right('foo'), + _.chainReaderKW((s) => R.of(s.length)) + ) + const r = 'foo' + const c = new MockConnection(new MockRequest({}, undefined, undefined, {})) + return assertSuccess(m1, r, c, 3, []) + }) + + it('chainReaderK', () => { + const m1 = pipe( + _.right('foo'), + _.chainReaderK((s) => R.of(s.length)) + ) + const r = 'foo' + const c = new MockConnection(new MockRequest({}, undefined, undefined, {})) + return assertSuccess(m1, r, c, 3, []) + }) + it('chainReaderTaskKW', () => { const m1 = pipe( _.right('foo'), From 7caa7e0cbdb83594026782056f7e972704ae43ce Mon Sep 17 00:00:00 2001 From: Chris Wilkinson Date: Mon, 24 Oct 2022 10:23:47 +0100 Subject: [PATCH 6/9] Add fromOption to ReaderMiddleware --- docs/modules/ReaderMiddleware.ts.md | 11 +++++++++++ src/ReaderMiddleware.ts | 8 ++++++++ test/Middleware.ts | 22 ++++++++++++++++++++++ test/ReaderMiddleware.ts | 20 ++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/docs/modules/ReaderMiddleware.ts.md b/docs/modules/ReaderMiddleware.ts.md index 6339abc..1f04d26 100644 --- a/docs/modules/ReaderMiddleware.ts.md +++ b/docs/modules/ReaderMiddleware.ts.md @@ -146,6 +146,7 @@ Added in v0.6.3 - [fromIO](#fromio) - [fromIOEither](#fromioeither) - [fromMiddleware](#frommiddleware) + - [fromOption](#fromoption) - [fromReaderTaskEither](#fromreadertaskeither) - [fromTask](#fromtask) - [fromTaskEither](#fromtaskeither) @@ -1688,6 +1689,16 @@ export declare const fromMiddleware: (onNone: Lazy) => (ma: O.Option) => ReaderMiddleware +``` + +Added in v0.7.9 + ## fromReaderTaskEither **Signature** diff --git a/src/ReaderMiddleware.ts b/src/ReaderMiddleware.ts index 6551b77..4b13bd3 100644 --- a/src/ReaderMiddleware.ts +++ b/src/ReaderMiddleware.ts @@ -27,6 +27,7 @@ import { chainOptionK as chainOptionK_, FromEither4, fromPredicate as fromPredicate_, + fromOption as fromOption_, filterOrElse as filterOrElse_, } from 'fp-ts/FromEither' import { FromIO4, fromIOK as fromIOK_, chainIOK as chainIOK_, chainFirstIOK as chainFirstIOK_ } from 'fp-ts/FromIO' @@ -1109,6 +1110,13 @@ export const filterOrElseW: { ) => ReaderMiddleware } = filterOrElse +/** + * @category natural transformations + * @since 0.7.9 + */ +export const fromOption: (onNone: Lazy) => (ma: O.Option) => ReaderMiddleware = + fromOption_(FromEither) + /** * @category combinators * @since 0.7.0 diff --git a/test/Middleware.ts b/test/Middleware.ts index ccfb16f..74c0f73 100644 --- a/test/Middleware.ts +++ b/test/Middleware.ts @@ -343,6 +343,28 @@ describe('Middleware', () => { }) }) + describe('fromOptionK', () => { + test('with a some', async () => { + const m = pipe( + O.some(8), + _.fromOption(() => 0) + ) + const c = new MockConnection(new MockRequest()) + return assertSuccess(m, c, 8, []) + }) + + test('with a none', async () => { + const m = pipe( + O.none, + _.fromOption(() => 'Some error') + ) + const c = new MockConnection(new MockRequest()) + return assertFailure(m, c, (error) => { + assert.strictEqual(error, 'Some error') + }) + }) + }) + describe('chainOptionK', () => { test('with a some', async () => { const m = pipe( diff --git a/test/ReaderMiddleware.ts b/test/ReaderMiddleware.ts index 0b2d4fd..ce29a26 100644 --- a/test/ReaderMiddleware.ts +++ b/test/ReaderMiddleware.ts @@ -601,6 +601,26 @@ describe('ReaderMiddleware', () => { return assertSuccess(m1, r, c, 'foo', []) }) + describe('fromOption', () => { + test('with a some', async () => { + const m = pipe( + O.some(8), + _.fromOption(() => 0) + ) + const c = new MockConnection(new MockRequest()) + return assertSuccess(m, undefined, c, 8, []) + }) + + test('with a none', async () => { + const m = pipe( + O.none, + _.fromOption(() => 'Some error') + ) + const c = new MockConnection(new MockRequest()) + return assertFailure(m, undefined, c, 'Some error') + }) + }) + describe('chainOptionK', () => { test('with a some', async () => { const m = pipe( From 96bf5ed3b8c23239fec256bd1c566449bfde9945 Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Fri, 2 Jun 2023 15:57:29 +0200 Subject: [PATCH 7/9] Fix missing error handling when using `pipeStream` --- src/express.ts | 5 +++-- test/express.ts | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/express.ts b/src/express.ts index 4e463a7..be80023 100644 --- a/src/express.ts +++ b/src/express.ts @@ -6,8 +6,9 @@ import { IncomingMessage } from 'http' import { Connection, CookieOptions, HeadersOpen, ResponseEnded, Status, StatusOpen } from '.' import { Middleware, execMiddleware } from './Middleware' import * as E from 'fp-ts/Either' -import { pipe } from 'fp-ts/function' +import { constUndefined, pipe } from 'fp-ts/function' import * as L from 'fp-ts-contrib/List' +import { pipeline } from 'stream' /** * @internal @@ -147,7 +148,7 @@ function run(res: Response, action: Action): Response { case 'setStatus': return res.status(action.status) case 'pipeStream': - return action.stream.pipe(res) + return pipeline(action.stream, res, constUndefined) } } diff --git a/test/express.ts b/test/express.ts index 535439f..34288a4 100644 --- a/test/express.ts +++ b/test/express.ts @@ -126,5 +126,26 @@ describe('express', () => { return supertest(server).get('/').expect(200, 'a') }) + + it('should handle piped stream error', () => { + const server = express() + const someStream = (): Readable => { + const stream = new Readable() + stream._read = () => { + stream.emit('error', new Error('abort')) + } + return stream + } + + const stream = someStream() + const m = pipe( + M.status(H.Status.OK), + M.ichain(() => M.closeHeaders()), + M.ichain(() => M.pipeStream(stream)) + ) + server.use(toRequestHandler(m)) + + return expect(supertest(server).get('/')).rejects.toThrowError('socket hang up') + }) }) }) From 0ff634d24a9bda401a0bbb6ce9497ff189b352eb Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Mon, 5 Jun 2023 12:46:57 +0200 Subject: [PATCH 8/9] Expose error handler on `pipeStream` --- docs/modules/Middleware.ts.md | 5 ++++- docs/modules/ReaderMiddleware.ts.md | 3 ++- docs/modules/express.ts.md | 2 +- docs/modules/index.ts.md | 6 +++++- src/Middleware.ts | 7 +++++-- src/ReaderMiddleware.ts | 9 +++++++-- src/express.ts | 14 +++++++++----- src/index.ts | 7 ++++++- test/Middleware.ts | 9 +++++---- test/express.ts | 5 +++-- 10 files changed, 47 insertions(+), 20 deletions(-) diff --git a/docs/modules/Middleware.ts.md b/docs/modules/Middleware.ts.md index b1462bc..5768801 100644 --- a/docs/modules/Middleware.ts.md +++ b/docs/modules/Middleware.ts.md @@ -1066,7 +1066,10 @@ Returns a middleware that pipes a stream to the response object. **Signature** ```ts -export declare function pipeStream(stream: NodeJS.ReadableStream): Middleware +export declare function pipeStream( + stream: NodeJS.ReadableStream, + onError: (err: unknown) => IO +): Middleware ``` Added in v0.7.0 diff --git a/docs/modules/ReaderMiddleware.ts.md b/docs/modules/ReaderMiddleware.ts.md index 6339abc..ba11236 100644 --- a/docs/modules/ReaderMiddleware.ts.md +++ b/docs/modules/ReaderMiddleware.ts.md @@ -1352,7 +1352,8 @@ Returns a `ReaderMiddleware` that pipes a stream to the response object. ```ts export declare function pipeStream( - stream: NodeJS.ReadableStream + stream: NodeJS.ReadableStream, + onError: (reason: unknown) => ReaderIO ): ReaderMiddleware ``` diff --git a/docs/modules/express.ts.md b/docs/modules/express.ts.md index 2dcba40..492401f 100644 --- a/docs/modules/express.ts.md +++ b/docs/modules/express.ts.md @@ -191,7 +191,7 @@ Added in v0.5.0 **Signature** ```ts -pipeStream(stream: NodeJS.ReadableStream): ExpressConnection +pipeStream(stream: NodeJS.ReadableStream, onError: (e: unknown) => IO.IO): ExpressConnection ``` Added in v0.6.2 diff --git a/docs/modules/index.ts.md b/docs/modules/index.ts.md index 9ade0da..89365be 100644 --- a/docs/modules/index.ts.md +++ b/docs/modules/index.ts.md @@ -685,7 +685,11 @@ export interface Connection { readonly setHeader: (this: Connection, name: string, value: string) => Connection readonly setStatus: (this: Connection, status: Status) => Connection readonly setBody: (this: Connection, body: string | Buffer) => Connection - readonly pipeStream: (this: Connection, stream: NodeJS.ReadableStream) => Connection + readonly pipeStream: ( + this: Connection, + stream: NodeJS.ReadableStream, + onError: (e: unknown) => IO + ) => Connection readonly endResponse: (this: Connection) => Connection } ``` diff --git a/src/Middleware.ts b/src/Middleware.ts index b4a3d07..cf41789 100644 --- a/src/Middleware.ts +++ b/src/Middleware.ts @@ -629,8 +629,11 @@ export function redirect(uri: string | { href: string }): Middleware< * @category constructors * @since 0.7.0 */ -export function pipeStream(stream: NodeJS.ReadableStream): Middleware { - return modifyConnection((c) => c.pipeStream(stream)) +export function pipeStream( + stream: NodeJS.ReadableStream, + onError: (err: unknown) => IO +): Middleware { + return modifyConnection((c) => c.pipeStream(stream, onError)) } const isUnknownRecord = (u: unknown): u is Record => u !== null && typeof u === 'object' diff --git a/src/ReaderMiddleware.ts b/src/ReaderMiddleware.ts index 6551b77..a8d96ee 100644 --- a/src/ReaderMiddleware.ts +++ b/src/ReaderMiddleware.ts @@ -36,6 +36,7 @@ import { chainTaskK as chainTaskK_, chainFirstTaskK as chainFirstTaskK_, } from 'fp-ts/FromTask' +import { ReaderIO } from 'fp-ts-contrib/ReaderIO' /** * @category instances @@ -440,9 +441,13 @@ export function redirect( * @since 0.7.3 */ export function pipeStream( - stream: NodeJS.ReadableStream + stream: NodeJS.ReadableStream, + onError: (reason: unknown) => ReaderIO ): ReaderMiddleware { - return modifyConnection((c) => c.pipeStream(stream)) + return pipe( + ask(), + ichain((r) => modifyConnection((c) => c.pipeStream(stream, (err) => onError(err)(r)))) + ) } /** diff --git a/src/express.ts b/src/express.ts index be80023..5a2738c 100644 --- a/src/express.ts +++ b/src/express.ts @@ -6,9 +6,11 @@ import { IncomingMessage } from 'http' import { Connection, CookieOptions, HeadersOpen, ResponseEnded, Status, StatusOpen } from '.' import { Middleware, execMiddleware } from './Middleware' import * as E from 'fp-ts/Either' -import { constUndefined, pipe } from 'fp-ts/function' +import { pipe } from 'fp-ts/function' import * as L from 'fp-ts-contrib/List' import { pipeline } from 'stream' +import * as IO from 'fp-ts/IO' +import * as O from 'fp-ts/Option' /** * @internal @@ -20,7 +22,7 @@ export type Action = | { type: 'setHeader'; name: string; value: string } | { type: 'clearCookie'; name: string; options: CookieOptions } | { type: 'setCookie'; name: string; value: string; options: CookieOptions } - | { type: 'pipeStream'; stream: NodeJS.ReadableStream } + | { type: 'pipeStream'; stream: NodeJS.ReadableStream; onError: (e: unknown) => IO.IO } const endResponse: Action = { type: 'endResponse' } @@ -120,8 +122,8 @@ export class ExpressConnection implements Connection { /** * @since 0.6.2 */ - pipeStream(stream: NodeJS.ReadableStream): ExpressConnection { - return this.chain({ type: 'pipeStream', stream }, true) + pipeStream(stream: NodeJS.ReadableStream, onError: (e: unknown) => IO.IO): ExpressConnection { + return this.chain({ type: 'pipeStream', stream, onError }, true) } /** * @since 0.5.0 @@ -148,7 +150,9 @@ function run(res: Response, action: Action): Response { case 'setStatus': return res.status(action.status) case 'pipeStream': - return pipeline(action.stream, res, constUndefined) + return pipeline(action.stream, res, (err) => + pipe(err, O.fromNullable, O.traverse(IO.Applicative)(action.onError))() + ) } } diff --git a/src/index.ts b/src/index.ts index 377d24e..dd27136 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import { MonadTask3 } from 'fp-ts/MonadTask' import { MonadThrow3 } from 'fp-ts/MonadThrow' import { IncomingMessage } from 'http' import * as M from './Middleware' +import { IO } from 'fp-ts/IO' /** * Adapted from https://github.com/purescript-contrib/purescript-media-types @@ -189,7 +190,11 @@ export interface Connection { readonly setHeader: (this: Connection, name: string, value: string) => Connection readonly setStatus: (this: Connection, status: Status) => Connection readonly setBody: (this: Connection, body: string | Buffer) => Connection - readonly pipeStream: (this: Connection, stream: NodeJS.ReadableStream) => Connection + readonly pipeStream: ( + this: Connection, + stream: NodeJS.ReadableStream, + onError: (e: unknown) => IO + ) => Connection readonly endResponse: (this: Connection) => Connection } diff --git a/test/Middleware.ts b/test/Middleware.ts index ccfb16f..5d37949 100644 --- a/test/Middleware.ts +++ b/test/Middleware.ts @@ -11,6 +11,7 @@ import { MockConnection, MockRequest } from './_helpers' import { Readable } from 'stream' import * as _ from '../src/Middleware' import * as L from 'fp-ts-contrib/List' +import * as C from 'fp-ts/Console' function assertSuccess(m: _.Middleware, cin: MockConnection, a: A, actions: Array) { return m(cin)().then((e) => { @@ -223,9 +224,9 @@ describe('Middleware', () => { } const stream = someStream() const c = new MockConnection(new MockRequest()) - const m = _.pipeStream(stream) + const m = _.pipeStream(stream, C.error) - return assertSuccess(m, c, undefined, [{ type: 'pipeStream', stream }]) + return assertSuccess(m, c, undefined, [{ type: 'pipeStream', stream, onError: C.error }]) }) it('should pipe a stream and handle the failure', () => { @@ -238,9 +239,9 @@ describe('Middleware', () => { } const stream = someStream() const c = new MockConnection(new MockRequest()) - const m = _.pipeStream(stream) + const m = _.pipeStream(stream, C.error) - return assertSuccess(m, c, undefined, [{ type: 'pipeStream', stream }]) + return assertSuccess(m, c, undefined, [{ type: 'pipeStream', stream, onError: C.error }]) }) }) diff --git a/test/express.ts b/test/express.ts index 34288a4..9f95a59 100644 --- a/test/express.ts +++ b/test/express.ts @@ -7,6 +7,7 @@ import * as express from 'express' import * as supertest from 'supertest' import * as t from 'io-ts' import * as E from 'fp-ts/Either' +import * as C from 'fp-ts/Console' describe('express', () => { it('should call `next` with an error', () => { @@ -120,7 +121,7 @@ describe('express', () => { const m = pipe( M.status(H.Status.OK), M.ichain(() => M.closeHeaders()), - M.ichain(() => M.pipeStream(stream)) + M.ichain(() => M.pipeStream(stream, C.error)) ) server.use(toRequestHandler(m)) @@ -141,7 +142,7 @@ describe('express', () => { const m = pipe( M.status(H.Status.OK), M.ichain(() => M.closeHeaders()), - M.ichain(() => M.pipeStream(stream)) + M.ichain(() => M.pipeStream(stream, C.error)) ) server.use(toRequestHandler(m)) From 3ccebeaf2700c4c65aedc8e0e7b51deceb53a159 Mon Sep 17 00:00:00 2001 From: Malte Legenhausen Date: Mon, 5 Jun 2023 14:41:21 +0200 Subject: [PATCH 9/9] Update `fp-ts` for native `ReaderIO` support --- docs/modules/Middleware.ts.md | 8 ++++---- docs/modules/ReaderMiddleware.ts.md | 4 ++-- package-lock.json | 15 ++++++++------- package.json | 2 +- src/Middleware.ts | 4 +++- src/ReaderMiddleware.ts | 6 ++++-- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/docs/modules/Middleware.ts.md b/docs/modules/Middleware.ts.md index 5768801..31a724e 100644 --- a/docs/modules/Middleware.ts.md +++ b/docs/modules/Middleware.ts.md @@ -439,8 +439,8 @@ Derivable from `Chain`. **Signature** ```ts -export declare const chainFirst: ( - f: (a: A) => Middleware +export declare const chainFirst: ( + f: (a: A) => Middleware ) => (first: Middleware) => Middleware ``` @@ -1364,7 +1364,7 @@ Added in v0.7.0 **Signature** ```ts -export declare const fromIO: (fa: IO) => Middleware +export declare const fromIO: (fa: IO) => Middleware ``` Added in v0.7.0 @@ -1394,7 +1394,7 @@ Added in v0.7.0 **Signature** ```ts -export declare const fromTask: (fa: T.Task) => Middleware +export declare const fromTask: (fa: T.Task) => Middleware ``` Added in v0.7.0 diff --git a/docs/modules/ReaderMiddleware.ts.md b/docs/modules/ReaderMiddleware.ts.md index ba11236..62d4223 100644 --- a/docs/modules/ReaderMiddleware.ts.md +++ b/docs/modules/ReaderMiddleware.ts.md @@ -1660,7 +1660,7 @@ Added in v0.7.0 **Signature** ```ts -export declare const fromIO: (fa: IO) => ReaderMiddleware +export declare const fromIO: (fa: IO) => ReaderMiddleware ``` Added in v0.7.0 @@ -1706,7 +1706,7 @@ Added in v0.6.3 **Signature** ```ts -export declare const fromTask: (fa: Task) => ReaderMiddleware +export declare const fromTask: (fa: Task) => ReaderMiddleware ``` Added in v0.7.0 diff --git a/package-lock.json b/package-lock.json index df8fa66..4d08783 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "docs-ts": "^0.6.10", "dtslint": "github:gcanti/dtslint", "express": "^4.17.1", - "fp-ts": "^2.10.0", + "fp-ts": "^2.13.2", "fp-ts-contrib": "^0.1.26", "fp-ts-routing": "^0.5.4", "husky": "^4.3.8", @@ -2933,9 +2933,9 @@ } }, "node_modules/fp-ts": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.10.5.tgz", - "integrity": "sha512-X2KfTIV0cxIk3d7/2Pvp/pxL/xr2MV1WooyEzKtTWYSc1+52VF4YzjBTXqeOlSiZsPCxIBpDGfT9Dyo7WEY0DQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.0.tgz", + "integrity": "sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ==", "dev": true }, "node_modules/fp-ts-contrib": { @@ -3006,6 +3006,7 @@ "node-pre-gyp" ], "dev": true, + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -12103,9 +12104,9 @@ "dev": true }, "fp-ts": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.10.5.tgz", - "integrity": "sha512-X2KfTIV0cxIk3d7/2Pvp/pxL/xr2MV1WooyEzKtTWYSc1+52VF4YzjBTXqeOlSiZsPCxIBpDGfT9Dyo7WEY0DQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.0.tgz", + "integrity": "sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ==", "dev": true }, "fp-ts-contrib": { diff --git a/package.json b/package.json index f10b6b1..bc4a521 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "docs-ts": "^0.6.10", "dtslint": "github:gcanti/dtslint", "express": "^4.17.1", - "fp-ts": "^2.10.0", + "fp-ts": "^2.13.2", "fp-ts-contrib": "^0.1.26", "fp-ts-routing": "^0.5.4", "husky": "^4.3.8", diff --git a/src/Middleware.ts b/src/Middleware.ts index cf41789..da03da1 100644 --- a/src/Middleware.ts +++ b/src/Middleware.ts @@ -11,7 +11,7 @@ import { Alt3 } from 'fp-ts/Alt' import { apFirst as apFirst_, apSecond as apSecond_, Apply3, apS as apS_ } from 'fp-ts/Apply' import { bind as bind_, Chain3, chainFirst as chainFirst_ } from 'fp-ts/Chain' import { Bifunctor3 } from 'fp-ts/Bifunctor' -import { identity, Lazy, pipe, Predicate, Refinement } from 'fp-ts/function' +import { identity, Lazy, pipe } from 'fp-ts/function' import { Functor3, bindTo as bindTo_ } from 'fp-ts/Functor' import { Monad3 } from 'fp-ts/Monad' import { BodyOpen, Connection, CookieOptions, HeadersOpen, MediaType, ResponseEnded, Status, StatusOpen } from '.' @@ -40,6 +40,8 @@ import { chainTaskK as chainTaskK_, chainFirstTaskK as chainFirstTaskK_, } from 'fp-ts/FromTask' +import { Refinement } from 'fp-ts/Refinement' +import { Predicate } from 'fp-ts/Predicate' declare module 'fp-ts/HKT' { interface URItoKind3 { diff --git a/src/ReaderMiddleware.ts b/src/ReaderMiddleware.ts index a8d96ee..2f97728 100644 --- a/src/ReaderMiddleware.ts +++ b/src/ReaderMiddleware.ts @@ -1,7 +1,7 @@ /** * @since 0.6.3 */ -import { flow, identity, Lazy, pipe, Predicate, Refinement } from 'fp-ts/function' +import { flow, identity, Lazy, pipe } from 'fp-ts/function' import { bind as bind_, chainFirst as chainFirst_, Chain4 } from 'fp-ts/Chain' import { ReaderTask } from 'fp-ts/ReaderTask' import { Task } from 'fp-ts/Task' @@ -36,7 +36,9 @@ import { chainTaskK as chainTaskK_, chainFirstTaskK as chainFirstTaskK_, } from 'fp-ts/FromTask' -import { ReaderIO } from 'fp-ts-contrib/ReaderIO' +import { ReaderIO } from 'fp-ts/ReaderIO' +import { Refinement } from 'fp-ts/Refinement' +import { Predicate } from 'fp-ts/Predicate' /** * @category instances