@effect/platform 0.68.6 → 0.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HttpApiMiddleware/package.json +6 -0
- package/README.md +306 -233
- package/dist/cjs/Headers.js +7 -2
- package/dist/cjs/Headers.js.map +1 -1
- package/dist/cjs/HttpApi.js +90 -78
- package/dist/cjs/HttpApi.js.map +1 -1
- package/dist/cjs/HttpApiBuilder.js +243 -255
- package/dist/cjs/HttpApiBuilder.js.map +1 -1
- package/dist/cjs/HttpApiClient.js +64 -59
- package/dist/cjs/HttpApiClient.js.map +1 -1
- package/dist/cjs/HttpApiEndpoint.js +74 -109
- package/dist/cjs/HttpApiEndpoint.js.map +1 -1
- package/dist/cjs/HttpApiError.js +3 -4
- package/dist/cjs/HttpApiError.js.map +1 -1
- package/dist/cjs/HttpApiGroup.js +103 -100
- package/dist/cjs/HttpApiGroup.js.map +1 -1
- package/dist/cjs/HttpApiMiddleware.js +67 -0
- package/dist/cjs/HttpApiMiddleware.js.map +1 -0
- package/dist/cjs/HttpApiSchema.js +33 -7
- package/dist/cjs/HttpApiSchema.js.map +1 -1
- package/dist/cjs/HttpApiSecurity.js +2 -2
- package/dist/cjs/HttpApiSecurity.js.map +1 -1
- package/dist/cjs/HttpApiSwagger.js +3 -1
- package/dist/cjs/HttpApiSwagger.js.map +1 -1
- package/dist/cjs/HttpBody.js.map +1 -1
- package/dist/cjs/HttpIncomingMessage.js +5 -1
- package/dist/cjs/HttpIncomingMessage.js.map +1 -1
- package/dist/cjs/HttpServer.js +12 -1
- package/dist/cjs/HttpServer.js.map +1 -1
- package/dist/cjs/HttpServerRespondable.js +1 -1
- package/dist/cjs/HttpServerRespondable.js.map +1 -1
- package/dist/cjs/OpenApi.js +102 -63
- package/dist/cjs/OpenApi.js.map +1 -1
- package/dist/cjs/OpenApiJsonSchema.js +58 -47
- package/dist/cjs/OpenApiJsonSchema.js.map +1 -1
- package/dist/cjs/Transferable.js +2 -2
- package/dist/cjs/Transferable.js.map +1 -1
- package/dist/cjs/UrlParams.js +5 -1
- package/dist/cjs/UrlParams.js.map +1 -1
- package/dist/cjs/Worker.js.map +1 -1
- package/dist/cjs/WorkerError.js +1 -5
- package/dist/cjs/WorkerError.js.map +1 -1
- package/dist/cjs/WorkerRunner.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/httpBody.js +1 -1
- package/dist/cjs/internal/httpBody.js.map +1 -1
- package/dist/cjs/internal/httpClientRequest.js.map +1 -1
- package/dist/cjs/internal/httpClientResponse.js +1 -1
- package/dist/cjs/internal/httpClientResponse.js.map +1 -1
- package/dist/cjs/internal/httpRouter.js +1 -1
- package/dist/cjs/internal/httpRouter.js.map +1 -1
- package/dist/cjs/internal/httpServer.js +7 -1
- package/dist/cjs/internal/httpServer.js.map +1 -1
- package/dist/cjs/internal/httpServerRequest.js +1 -1
- package/dist/cjs/internal/httpServerRequest.js.map +1 -1
- package/dist/cjs/internal/httpServerResponse.js.map +1 -1
- package/dist/cjs/internal/keyValueStore.js +1 -1
- package/dist/cjs/internal/keyValueStore.js.map +1 -1
- package/dist/cjs/internal/multipart.js +1 -1
- package/dist/cjs/internal/multipart.js.map +1 -1
- package/dist/cjs/internal/worker.js +6 -7
- package/dist/cjs/internal/worker.js.map +1 -1
- package/dist/cjs/internal/workerRunner.js +3 -4
- package/dist/cjs/internal/workerRunner.js.map +1 -1
- package/dist/dts/Headers.d.ts +4 -6
- package/dist/dts/Headers.d.ts.map +1 -1
- package/dist/dts/HttpApi.d.ts +64 -140
- package/dist/dts/HttpApi.d.ts.map +1 -1
- package/dist/dts/HttpApiBuilder.d.ts +84 -167
- package/dist/dts/HttpApiBuilder.d.ts.map +1 -1
- package/dist/dts/HttpApiClient.d.ts +34 -11
- package/dist/dts/HttpApiClient.d.ts.map +1 -1
- package/dist/dts/HttpApiEndpoint.d.ts +119 -273
- package/dist/dts/HttpApiEndpoint.d.ts.map +1 -1
- package/dist/dts/HttpApiError.d.ts +5 -2
- package/dist/dts/HttpApiError.d.ts.map +1 -1
- package/dist/dts/HttpApiGroup.d.ts +96 -194
- package/dist/dts/HttpApiGroup.d.ts.map +1 -1
- package/dist/dts/HttpApiMiddleware.d.ts +231 -0
- package/dist/dts/HttpApiMiddleware.d.ts.map +1 -0
- package/dist/dts/HttpApiSchema.d.ts +6 -2
- package/dist/dts/HttpApiSchema.d.ts.map +1 -1
- package/dist/dts/HttpApiSecurity.d.ts +1 -1
- package/dist/dts/HttpApiSecurity.d.ts.map +1 -1
- package/dist/dts/HttpApiSwagger.d.ts +2 -2
- package/dist/dts/HttpApiSwagger.d.ts.map +1 -1
- package/dist/dts/HttpBody.d.ts +2 -2
- package/dist/dts/HttpBody.d.ts.map +1 -1
- package/dist/dts/HttpClientRequest.d.ts +2 -2
- package/dist/dts/HttpClientRequest.d.ts.map +1 -1
- package/dist/dts/HttpClientResponse.d.ts +3 -3
- package/dist/dts/HttpClientResponse.d.ts.map +1 -1
- package/dist/dts/HttpIncomingMessage.d.ts +3 -3
- package/dist/dts/HttpIncomingMessage.d.ts.map +1 -1
- package/dist/dts/HttpRouter.d.ts +3 -3
- package/dist/dts/HttpRouter.d.ts.map +1 -1
- package/dist/dts/HttpServer.d.ts +15 -0
- package/dist/dts/HttpServer.d.ts.map +1 -1
- package/dist/dts/HttpServerRequest.d.ts +3 -3
- package/dist/dts/HttpServerRequest.d.ts.map +1 -1
- package/dist/dts/HttpServerRespondable.d.ts.map +1 -1
- package/dist/dts/HttpServerResponse.d.ts +2 -2
- package/dist/dts/HttpServerResponse.d.ts.map +1 -1
- package/dist/dts/KeyValueStore.d.ts +2 -2
- package/dist/dts/KeyValueStore.d.ts.map +1 -1
- package/dist/dts/Multipart.d.ts +3 -3
- package/dist/dts/Multipart.d.ts.map +1 -1
- package/dist/dts/OpenApi.d.ts +17 -39
- package/dist/dts/OpenApi.d.ts.map +1 -1
- package/dist/dts/OpenApiJsonSchema.d.ts +10 -5
- package/dist/dts/OpenApiJsonSchema.d.ts.map +1 -1
- package/dist/dts/Transferable.d.ts +4 -1
- package/dist/dts/Transferable.d.ts.map +1 -1
- package/dist/dts/UrlParams.d.ts +3 -6
- package/dist/dts/UrlParams.d.ts.map +1 -1
- package/dist/dts/Worker.d.ts +7 -8
- package/dist/dts/Worker.d.ts.map +1 -1
- package/dist/dts/WorkerError.d.ts +1 -1
- package/dist/dts/WorkerError.d.ts.map +1 -1
- package/dist/dts/WorkerRunner.d.ts +2 -3
- package/dist/dts/WorkerRunner.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/httpRouter.d.ts.map +1 -1
- package/dist/esm/Headers.js +7 -2
- package/dist/esm/Headers.js.map +1 -1
- package/dist/esm/HttpApi.js +88 -77
- package/dist/esm/HttpApi.js.map +1 -1
- package/dist/esm/HttpApiBuilder.js +236 -244
- package/dist/esm/HttpApiBuilder.js.map +1 -1
- package/dist/esm/HttpApiClient.js +64 -59
- package/dist/esm/HttpApiClient.js.map +1 -1
- package/dist/esm/HttpApiEndpoint.js +73 -106
- package/dist/esm/HttpApiEndpoint.js.map +1 -1
- package/dist/esm/HttpApiError.js +3 -4
- package/dist/esm/HttpApiError.js.map +1 -1
- package/dist/esm/HttpApiGroup.js +102 -99
- package/dist/esm/HttpApiGroup.js.map +1 -1
- package/dist/esm/HttpApiMiddleware.js +56 -0
- package/dist/esm/HttpApiMiddleware.js.map +1 -0
- package/dist/esm/HttpApiSchema.js +31 -5
- package/dist/esm/HttpApiSchema.js.map +1 -1
- package/dist/esm/HttpApiSecurity.js +1 -1
- package/dist/esm/HttpApiSecurity.js.map +1 -1
- package/dist/esm/HttpApiSwagger.js +4 -2
- package/dist/esm/HttpApiSwagger.js.map +1 -1
- package/dist/esm/HttpBody.js.map +1 -1
- package/dist/esm/HttpIncomingMessage.js +4 -1
- package/dist/esm/HttpIncomingMessage.js.map +1 -1
- package/dist/esm/HttpServer.js +11 -0
- package/dist/esm/HttpServer.js.map +1 -1
- package/dist/esm/HttpServerRespondable.js +1 -1
- package/dist/esm/HttpServerRespondable.js.map +1 -1
- package/dist/esm/OpenApi.js +97 -59
- package/dist/esm/OpenApi.js.map +1 -1
- package/dist/esm/OpenApiJsonSchema.js +56 -46
- package/dist/esm/OpenApiJsonSchema.js.map +1 -1
- package/dist/esm/Transferable.js +2 -2
- package/dist/esm/Transferable.js.map +1 -1
- package/dist/esm/UrlParams.js +4 -1
- package/dist/esm/UrlParams.js.map +1 -1
- package/dist/esm/Worker.js.map +1 -1
- package/dist/esm/WorkerError.js +1 -4
- package/dist/esm/WorkerError.js.map +1 -1
- package/dist/esm/WorkerRunner.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/httpBody.js +1 -1
- package/dist/esm/internal/httpBody.js.map +1 -1
- package/dist/esm/internal/httpClientRequest.js.map +1 -1
- package/dist/esm/internal/httpClientResponse.js +1 -1
- package/dist/esm/internal/httpClientResponse.js.map +1 -1
- package/dist/esm/internal/httpRouter.js +1 -1
- package/dist/esm/internal/httpRouter.js.map +1 -1
- package/dist/esm/internal/httpServer.js +6 -0
- package/dist/esm/internal/httpServer.js.map +1 -1
- package/dist/esm/internal/httpServerRequest.js +1 -1
- package/dist/esm/internal/httpServerRequest.js.map +1 -1
- package/dist/esm/internal/httpServerResponse.js.map +1 -1
- package/dist/esm/internal/keyValueStore.js +1 -1
- package/dist/esm/internal/keyValueStore.js.map +1 -1
- package/dist/esm/internal/multipart.js +1 -1
- package/dist/esm/internal/multipart.js.map +1 -1
- package/dist/esm/internal/worker.js +6 -7
- package/dist/esm/internal/worker.js.map +1 -1
- package/dist/esm/internal/workerRunner.js +3 -4
- package/dist/esm/internal/workerRunner.js.map +1 -1
- package/package.json +10 -3
- package/src/Headers.ts +12 -4
- package/src/HttpApi.ts +183 -258
- package/src/HttpApiBuilder.ts +532 -481
- package/src/HttpApiClient.ts +163 -112
- package/src/HttpApiEndpoint.ts +443 -564
- package/src/HttpApiError.ts +4 -6
- package/src/HttpApiGroup.ts +277 -325
- package/src/HttpApiMiddleware.ts +318 -0
- package/src/HttpApiSchema.ts +39 -2
- package/src/HttpApiSecurity.ts +1 -1
- package/src/HttpApiSwagger.ts +3 -3
- package/src/HttpBody.ts +2 -2
- package/src/HttpClientRequest.ts +2 -2
- package/src/HttpClientResponse.ts +3 -3
- package/src/HttpIncomingMessage.ts +3 -3
- package/src/HttpRouter.ts +3 -3
- package/src/HttpServer.ts +21 -0
- package/src/HttpServerRequest.ts +3 -3
- package/src/HttpServerRespondable.ts +1 -1
- package/src/HttpServerResponse.ts +2 -2
- package/src/KeyValueStore.ts +2 -2
- package/src/Multipart.ts +3 -3
- package/src/OpenApi.ts +113 -104
- package/src/OpenApiJsonSchema.ts +67 -53
- package/src/Transferable.ts +2 -2
- package/src/UrlParams.ts +3 -3
- package/src/Worker.ts +7 -8
- package/src/WorkerError.ts +1 -1
- package/src/WorkerRunner.ts +2 -3
- package/src/index.ts +5 -0
- package/src/internal/httpBody.ts +2 -2
- package/src/internal/httpClientRequest.ts +2 -2
- package/src/internal/httpClientResponse.ts +3 -3
- package/src/internal/httpRouter.ts +2 -2
- package/src/internal/httpServer.ts +13 -0
- package/src/internal/httpServerRequest.ts +3 -3
- package/src/internal/httpServerResponse.ts +2 -2
- package/src/internal/keyValueStore.ts +1 -1
- package/src/internal/multipart.ts +3 -3
- package/src/internal/worker.ts +6 -7
- package/src/internal/workerRunner.ts +3 -4
package/src/HttpApiBuilder.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
|
-
import * as AST from "@effect/schema/AST"
|
|
5
|
-
import * as ParseResult from "@effect/schema/ParseResult"
|
|
6
|
-
import * as Schema from "@effect/schema/Schema"
|
|
7
4
|
import * as Chunk from "effect/Chunk"
|
|
8
5
|
import * as Context from "effect/Context"
|
|
9
6
|
import * as Effect from "effect/Effect"
|
|
@@ -11,21 +8,27 @@ import * as Encoding from "effect/Encoding"
|
|
|
11
8
|
import * as FiberRef from "effect/FiberRef"
|
|
12
9
|
import { identity } from "effect/Function"
|
|
13
10
|
import { globalValue } from "effect/GlobalValue"
|
|
11
|
+
import * as HashMap from "effect/HashMap"
|
|
12
|
+
import * as HashSet from "effect/HashSet"
|
|
14
13
|
import * as Layer from "effect/Layer"
|
|
15
|
-
import
|
|
14
|
+
import * as ManagedRuntime from "effect/ManagedRuntime"
|
|
16
15
|
import * as Option from "effect/Option"
|
|
16
|
+
import * as ParseResult from "effect/ParseResult"
|
|
17
17
|
import { type Pipeable, pipeArguments } from "effect/Pipeable"
|
|
18
18
|
import type { ReadonlyRecord } from "effect/Record"
|
|
19
19
|
import * as Redacted from "effect/Redacted"
|
|
20
|
+
import * as Schema from "effect/Schema"
|
|
21
|
+
import type * as AST from "effect/SchemaAST"
|
|
20
22
|
import type { Scope } from "effect/Scope"
|
|
21
|
-
import type { Covariant,
|
|
23
|
+
import type { Covariant, NoInfer } from "effect/Types"
|
|
22
24
|
import { unify } from "effect/Unify"
|
|
23
25
|
import type { Cookie } from "./Cookies.js"
|
|
24
26
|
import type { FileSystem } from "./FileSystem.js"
|
|
25
27
|
import * as HttpApi from "./HttpApi.js"
|
|
26
|
-
import * as HttpApiEndpoint from "./HttpApiEndpoint.js"
|
|
28
|
+
import type * as HttpApiEndpoint from "./HttpApiEndpoint.js"
|
|
27
29
|
import { HttpApiDecodeError } from "./HttpApiError.js"
|
|
28
30
|
import type * as HttpApiGroup from "./HttpApiGroup.js"
|
|
31
|
+
import * as HttpApiMiddleware from "./HttpApiMiddleware.js"
|
|
29
32
|
import * as HttpApiSchema from "./HttpApiSchema.js"
|
|
30
33
|
import type * as HttpApiSecurity from "./HttpApiSecurity.js"
|
|
31
34
|
import * as HttpApp from "./HttpApp.js"
|
|
@@ -46,6 +49,24 @@ import type { Path } from "./Path.js"
|
|
|
46
49
|
*/
|
|
47
50
|
export class Router extends HttpRouter.Tag("@effect/platform/HttpApiBuilder/Router")<Router>() {}
|
|
48
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Create a top-level `HttpApi` layer.
|
|
54
|
+
*
|
|
55
|
+
* @since 1.0.0
|
|
56
|
+
* @category constructors
|
|
57
|
+
*/
|
|
58
|
+
export const api = <Groups extends HttpApiGroup.HttpApiGroup.Any, E, R>(
|
|
59
|
+
api: HttpApi.HttpApi<Groups, E, R>
|
|
60
|
+
): Layer.Layer<
|
|
61
|
+
HttpApi.Api,
|
|
62
|
+
never,
|
|
63
|
+
HttpApiGroup.HttpApiGroup.ToService<Groups> | R | HttpApiGroup.HttpApiGroup.ErrorContext<Groups>
|
|
64
|
+
> =>
|
|
65
|
+
Layer.effect(
|
|
66
|
+
HttpApi.Api,
|
|
67
|
+
Effect.map(Effect.context(), (context) => ({ api: api as any, context }))
|
|
68
|
+
)
|
|
69
|
+
|
|
49
70
|
/**
|
|
50
71
|
* Build an `HttpApp` from an `HttpApi` instance, and serve it using an
|
|
51
72
|
* `HttpServer`.
|
|
@@ -56,43 +77,18 @@ export class Router extends HttpRouter.Tag("@effect/platform/HttpApiBuilder/Rout
|
|
|
56
77
|
* @since 1.0.0
|
|
57
78
|
* @category constructors
|
|
58
79
|
*/
|
|
59
|
-
export const serve
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* `HttpServer`.
|
|
63
|
-
*
|
|
64
|
-
* Optionally, you can provide a middleware function that will be applied to
|
|
65
|
-
* the `HttpApp` before serving.
|
|
66
|
-
*
|
|
67
|
-
* @since 1.0.0
|
|
68
|
-
* @category constructors
|
|
69
|
-
*/
|
|
70
|
-
(): Layer.Layer<never, never, HttpServer.HttpServer | HttpApi.HttpApi.Service | HttpRouter.HttpRouter.DefaultServices>
|
|
71
|
-
/**
|
|
72
|
-
* Build an `HttpApp` from an `HttpApi` instance, and serve it using an
|
|
73
|
-
* `HttpServer`.
|
|
74
|
-
*
|
|
75
|
-
* Optionally, you can provide a middleware function that will be applied to
|
|
76
|
-
* the `HttpApp` before serving.
|
|
77
|
-
*
|
|
78
|
-
* @since 1.0.0
|
|
79
|
-
* @category constructors
|
|
80
|
-
*/
|
|
81
|
-
<R>(middleware: (httpApp: HttpApp.Default) => HttpApp.Default<never, R>): Layer.Layer<
|
|
82
|
-
never,
|
|
83
|
-
never,
|
|
84
|
-
| HttpServer.HttpServer
|
|
85
|
-
| HttpRouter.HttpRouter.DefaultServices
|
|
86
|
-
| Exclude<R, Scope | HttpServerRequest.HttpServerRequest>
|
|
87
|
-
| HttpApi.HttpApi.Service
|
|
88
|
-
>
|
|
89
|
-
} = (middleware?: HttpMiddleware.HttpMiddleware.Applied<any, never, any>): Layer.Layer<
|
|
80
|
+
export const serve = <R = never>(
|
|
81
|
+
middleware?: (httpApp: HttpApp.Default) => HttpApp.Default<never, R>
|
|
82
|
+
): Layer.Layer<
|
|
90
83
|
never,
|
|
91
84
|
never,
|
|
92
|
-
|
|
85
|
+
| HttpServer.HttpServer
|
|
86
|
+
| HttpRouter.HttpRouter.DefaultServices
|
|
87
|
+
| Exclude<R, Scope | HttpServerRequest.HttpServerRequest>
|
|
88
|
+
| HttpApi.Api
|
|
93
89
|
> =>
|
|
94
90
|
httpApp.pipe(
|
|
95
|
-
Effect.map(HttpServer.serve(middleware!)),
|
|
91
|
+
Effect.map((app) => HttpServer.serve(app as any, middleware!)),
|
|
96
92
|
Layer.unwrapEffect,
|
|
97
93
|
Layer.provide(Router.Live)
|
|
98
94
|
)
|
|
@@ -106,17 +102,18 @@ export const serve: {
|
|
|
106
102
|
export const httpApp: Effect.Effect<
|
|
107
103
|
HttpApp.Default<never, HttpRouter.HttpRouter.DefaultServices>,
|
|
108
104
|
never,
|
|
109
|
-
Router | HttpApi.
|
|
105
|
+
Router | HttpApi.Api
|
|
110
106
|
> = Effect.gen(function*() {
|
|
111
|
-
const api = yield* HttpApi.
|
|
112
|
-
const
|
|
107
|
+
const { api, context } = yield* HttpApi.Api
|
|
108
|
+
const middleware = makeMiddlewareMap(api.middlewares, context)
|
|
109
|
+
const router = applyMiddleware(middleware, yield* Router.router)
|
|
113
110
|
const apiMiddleware = yield* Effect.serviceOption(Middleware)
|
|
114
111
|
const errorSchema = makeErrorSchema(api as any)
|
|
115
112
|
const encodeError = Schema.encodeUnknown(errorSchema)
|
|
116
113
|
return router.pipe(
|
|
117
114
|
apiMiddleware._tag === "Some" ? apiMiddleware.value : identity,
|
|
118
115
|
Effect.catchAll((error) =>
|
|
119
|
-
Effect.matchEffect(encodeError(error), {
|
|
116
|
+
Effect.matchEffect(Effect.provide(encodeError(error), context), {
|
|
120
117
|
onFailure: () => Effect.die(error),
|
|
121
118
|
onSuccess: Effect.succeed
|
|
122
119
|
})
|
|
@@ -130,59 +127,60 @@ export const httpApp: Effect.Effect<
|
|
|
130
127
|
* @since 1.0.0
|
|
131
128
|
* @category constructors
|
|
132
129
|
* @example
|
|
133
|
-
* import { HttpApi } from "@effect/platform"
|
|
134
|
-
* import {
|
|
135
|
-
*
|
|
136
|
-
*
|
|
130
|
+
* import { HttpApi, HttpApiBuilder, HttpServer } from "@effect/platform"
|
|
131
|
+
* import { Layer } from "effect"
|
|
132
|
+
*
|
|
133
|
+
* class MyApi extends HttpApi.empty {}
|
|
137
134
|
*
|
|
138
|
-
* const
|
|
135
|
+
* const MyApiLive = HttpApiBuilder.api(MyApi)
|
|
139
136
|
*
|
|
140
|
-
* const
|
|
137
|
+
* const { dispose, handler } = HttpApiBuilder.toWebHandler(
|
|
141
138
|
* Layer.mergeAll(
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* HttpPlatform.layer,
|
|
145
|
-
* Etag.layerWeak
|
|
146
|
-
* ).pipe(
|
|
147
|
-
* Layer.provideMerge(NodeContext.layer)
|
|
139
|
+
* MyApiLive,
|
|
140
|
+
* HttpServer.layerContext
|
|
148
141
|
* )
|
|
149
142
|
* )
|
|
150
|
-
*
|
|
151
|
-
* const handler = HttpApiBuilder.toWebHandler(runtime, HttpMiddleware.logger)
|
|
152
143
|
*/
|
|
153
|
-
export const toWebHandler = <
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
)
|
|
144
|
+
export const toWebHandler = <LA, LE>(
|
|
145
|
+
layer: Layer.Layer<LA | HttpApi.Api | HttpRouter.HttpRouter.DefaultServices, LE>,
|
|
146
|
+
options?: {
|
|
147
|
+
readonly middleware?: (
|
|
148
|
+
httpApp: HttpApp.Default
|
|
149
|
+
) => HttpApp.Default<
|
|
150
|
+
never,
|
|
151
|
+
HttpApi.Api | Router | HttpRouter.HttpRouter.DefaultServices
|
|
152
|
+
>
|
|
153
|
+
readonly memoMap?: Layer.MemoMap
|
|
154
|
+
}
|
|
155
|
+
): {
|
|
156
|
+
readonly handler: (request: Request) => Promise<Response>
|
|
157
|
+
readonly dispose: () => Promise<void>
|
|
158
|
+
} => {
|
|
159
|
+
const runtime = ManagedRuntime.make(
|
|
160
|
+
Layer.merge(layer, Router.Live),
|
|
161
|
+
options?.memoMap
|
|
162
|
+
)
|
|
163
|
+
let handlerCached: ((request: Request) => Promise<Response>) | undefined
|
|
159
164
|
const handlerPromise = httpApp.pipe(
|
|
160
165
|
Effect.bindTo("httpApp"),
|
|
161
166
|
Effect.bind("runtime", () => runtime.runtimeEffect),
|
|
162
167
|
Effect.map(({ httpApp, runtime }) =>
|
|
163
|
-
HttpApp.toWebHandlerRuntime(runtime)(middleware ? middleware(httpApp as any) : httpApp)
|
|
168
|
+
HttpApp.toWebHandlerRuntime(runtime)(options?.middleware ? options.middleware(httpApp as any) as any : httpApp)
|
|
164
169
|
),
|
|
170
|
+
Effect.tap((handler) => {
|
|
171
|
+
handlerCached = handler
|
|
172
|
+
}),
|
|
165
173
|
runtime.runPromise
|
|
166
174
|
)
|
|
167
|
-
|
|
175
|
+
function handler(request: Request): Promise<Response> {
|
|
176
|
+
if (handlerCached !== undefined) {
|
|
177
|
+
return handlerCached(request)
|
|
178
|
+
}
|
|
179
|
+
return handlerPromise.then((handler) => handler(request))
|
|
180
|
+
}
|
|
181
|
+
return { handler, dispose: runtime.dispose } as const
|
|
168
182
|
}
|
|
169
183
|
|
|
170
|
-
/**
|
|
171
|
-
* Build a root level `Layer` from an `HttpApi` instance.
|
|
172
|
-
*
|
|
173
|
-
* The `Layer` will provide the `HttpApi` service, and will require the
|
|
174
|
-
* implementation for all the `HttpApiGroup`'s contained in the `HttpApi`.
|
|
175
|
-
*
|
|
176
|
-
* The resulting `Layer` can be provided to the `HttpApiBuilder.serve` layer.
|
|
177
|
-
*
|
|
178
|
-
* @since 1.0.0
|
|
179
|
-
* @category constructors
|
|
180
|
-
*/
|
|
181
|
-
export const api = <Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR>(
|
|
182
|
-
self: HttpApi.HttpApi<Groups, Error, ErrorR>
|
|
183
|
-
): Layer.Layer<HttpApi.HttpApi.Service, never, HttpApiGroup.HttpApiGroup.ToService<Groups> | ErrorR> =>
|
|
184
|
-
Layer.succeed(HttpApi.HttpApi, self) as any
|
|
185
|
-
|
|
186
184
|
/**
|
|
187
185
|
* @since 1.0.0
|
|
188
186
|
* @category handlers
|
|
@@ -196,21 +194,49 @@ export const HandlersTypeId: unique symbol = Symbol.for("@effect/platform/HttpAp
|
|
|
196
194
|
export type HandlersTypeId = typeof HandlersTypeId
|
|
197
195
|
|
|
198
196
|
/**
|
|
199
|
-
* Represents a handled
|
|
197
|
+
* Represents a handled `HttpApi`.
|
|
200
198
|
*
|
|
201
199
|
* @since 1.0.0
|
|
202
200
|
* @category handlers
|
|
203
201
|
*/
|
|
204
202
|
export interface Handlers<
|
|
205
203
|
E,
|
|
204
|
+
Provides,
|
|
206
205
|
R,
|
|
207
|
-
Endpoints extends HttpApiEndpoint.HttpApiEndpoint.
|
|
206
|
+
Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Any = never
|
|
208
207
|
> extends Pipeable {
|
|
209
208
|
readonly [HandlersTypeId]: {
|
|
210
209
|
_Endpoints: Covariant<Endpoints>
|
|
211
210
|
}
|
|
212
|
-
readonly group: HttpApiGroup.HttpApiGroup
|
|
211
|
+
readonly group: HttpApiGroup.HttpApiGroup.AnyWithProps
|
|
213
212
|
readonly handlers: Chunk.Chunk<Handlers.Item<E, R>>
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
|
|
216
|
+
*/
|
|
217
|
+
handle<Name extends HttpApiEndpoint.HttpApiEndpoint.Name<Endpoints>, R1>(
|
|
218
|
+
name: Name,
|
|
219
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.HandlerWithName<Endpoints, Name, E, R1>
|
|
220
|
+
): Handlers<
|
|
221
|
+
E,
|
|
222
|
+
Provides,
|
|
223
|
+
R | Exclude<HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<Endpoints, Name, R1>, Provides>,
|
|
224
|
+
HttpApiEndpoint.HttpApiEndpoint.ExcludeName<Endpoints, Name>
|
|
225
|
+
>
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
|
|
229
|
+
* This version of the api allows you to return the full response object.
|
|
230
|
+
*/
|
|
231
|
+
handleRaw<Name extends HttpApiEndpoint.HttpApiEndpoint.Name<Endpoints>, R1>(
|
|
232
|
+
name: Name,
|
|
233
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.HandlerResponseWithName<Endpoints, Name, E, R1>
|
|
234
|
+
): Handlers<
|
|
235
|
+
E,
|
|
236
|
+
Provides,
|
|
237
|
+
R | Exclude<HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<Endpoints, Name, R1>, Provides>,
|
|
238
|
+
HttpApiEndpoint.HttpApiEndpoint.ExcludeName<Endpoints, Name>
|
|
239
|
+
>
|
|
214
240
|
}
|
|
215
241
|
|
|
216
242
|
/**
|
|
@@ -218,6 +244,14 @@ export interface Handlers<
|
|
|
218
244
|
* @category handlers
|
|
219
245
|
*/
|
|
220
246
|
export declare namespace Handlers {
|
|
247
|
+
/**
|
|
248
|
+
* @since 1.0.0
|
|
249
|
+
* @category handlers
|
|
250
|
+
*/
|
|
251
|
+
export interface Any {
|
|
252
|
+
readonly [HandlersTypeId]: any
|
|
253
|
+
}
|
|
254
|
+
|
|
221
255
|
/**
|
|
222
256
|
* @since 1.0.0
|
|
223
257
|
* @category handlers
|
|
@@ -229,14 +263,90 @@ export declare namespace Handlers {
|
|
|
229
263
|
* @category handlers
|
|
230
264
|
*/
|
|
231
265
|
export type Item<E, R> = {
|
|
232
|
-
readonly _tag: "Handler"
|
|
233
266
|
readonly endpoint: HttpApiEndpoint.HttpApiEndpoint.Any
|
|
234
267
|
readonly handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, E, R>
|
|
235
268
|
readonly withFullResponse: boolean
|
|
236
|
-
} | {
|
|
237
|
-
readonly _tag: "Middleware"
|
|
238
|
-
readonly middleware: Middleware<any, any, E, R>
|
|
239
269
|
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @since 1.0.0
|
|
273
|
+
* @category handlers
|
|
274
|
+
*/
|
|
275
|
+
export type FromGroup<
|
|
276
|
+
ApiError,
|
|
277
|
+
ApiR,
|
|
278
|
+
Group extends HttpApiGroup.HttpApiGroup.Any
|
|
279
|
+
> = Handlers<
|
|
280
|
+
| ApiError
|
|
281
|
+
| HttpApiGroup.HttpApiGroup.Error<Group>,
|
|
282
|
+
| HttpApiMiddleware.HttpApiMiddleware.ExtractProvides<ApiR>
|
|
283
|
+
| HttpApiGroup.HttpApiGroup.Provides<Group>,
|
|
284
|
+
never,
|
|
285
|
+
HttpApiGroup.HttpApiGroup.Endpoints<Group>
|
|
286
|
+
>
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @since 1.0.0
|
|
290
|
+
* @category handlers
|
|
291
|
+
*/
|
|
292
|
+
export type ValidateReturn<A> = A extends (
|
|
293
|
+
| Handlers<
|
|
294
|
+
infer _E,
|
|
295
|
+
infer _Provides,
|
|
296
|
+
infer _R,
|
|
297
|
+
infer _Endpoints
|
|
298
|
+
>
|
|
299
|
+
| Effect.Effect<
|
|
300
|
+
Handlers<
|
|
301
|
+
infer _E,
|
|
302
|
+
infer _Provides,
|
|
303
|
+
infer _R,
|
|
304
|
+
infer _Endpoints
|
|
305
|
+
>,
|
|
306
|
+
infer _EX,
|
|
307
|
+
infer _RX
|
|
308
|
+
>
|
|
309
|
+
) ? [_Endpoints] extends [never] ? A
|
|
310
|
+
: `Endpoint not handled: ${HttpApiEndpoint.HttpApiEndpoint.Name<_Endpoints>}` :
|
|
311
|
+
`Must return the implemented handlers`
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @since 1.0.0
|
|
315
|
+
* @category handlers
|
|
316
|
+
*/
|
|
317
|
+
export type Error<A> = A extends Effect.Effect<
|
|
318
|
+
Handlers<
|
|
319
|
+
infer _E,
|
|
320
|
+
infer _Provides,
|
|
321
|
+
infer _R,
|
|
322
|
+
infer _Endpoints
|
|
323
|
+
>,
|
|
324
|
+
infer _EX,
|
|
325
|
+
infer _RX
|
|
326
|
+
> ? _EX :
|
|
327
|
+
never
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @since 1.0.0
|
|
331
|
+
* @category handlers
|
|
332
|
+
*/
|
|
333
|
+
export type Context<A> = A extends Handlers<
|
|
334
|
+
infer _E,
|
|
335
|
+
infer _Provides,
|
|
336
|
+
infer _R,
|
|
337
|
+
infer _Endpoints
|
|
338
|
+
> ? _R :
|
|
339
|
+
A extends Effect.Effect<
|
|
340
|
+
Handlers<
|
|
341
|
+
infer _E,
|
|
342
|
+
infer _Provides,
|
|
343
|
+
infer _R,
|
|
344
|
+
infer _Endpoints
|
|
345
|
+
>,
|
|
346
|
+
infer _EX,
|
|
347
|
+
infer _RX
|
|
348
|
+
> ? _R | _RX :
|
|
349
|
+
never
|
|
240
350
|
}
|
|
241
351
|
|
|
242
352
|
const HandlersProto = {
|
|
@@ -245,15 +355,45 @@ const HandlersProto = {
|
|
|
245
355
|
},
|
|
246
356
|
pipe() {
|
|
247
357
|
return pipeArguments(this, arguments)
|
|
358
|
+
},
|
|
359
|
+
handle(
|
|
360
|
+
this: Handlers<any, any, any, HttpApiEndpoint.HttpApiEndpoint.Any>,
|
|
361
|
+
name: string,
|
|
362
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, any, any>
|
|
363
|
+
) {
|
|
364
|
+
const endpoint = HashMap.unsafeGet(this.group.endpoints, name)
|
|
365
|
+
return makeHandlers({
|
|
366
|
+
group: this.group,
|
|
367
|
+
handlers: Chunk.append(this.handlers, {
|
|
368
|
+
endpoint,
|
|
369
|
+
handler,
|
|
370
|
+
withFullResponse: false
|
|
371
|
+
}) as any
|
|
372
|
+
})
|
|
373
|
+
},
|
|
374
|
+
handleRaw(
|
|
375
|
+
this: Handlers<any, any, any, HttpApiEndpoint.HttpApiEndpoint.Any>,
|
|
376
|
+
name: string,
|
|
377
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, any, any>
|
|
378
|
+
) {
|
|
379
|
+
const endpoint = HashMap.unsafeGet(this.group.endpoints, name)
|
|
380
|
+
return makeHandlers({
|
|
381
|
+
group: this.group,
|
|
382
|
+
handlers: Chunk.append(this.handlers, {
|
|
383
|
+
endpoint,
|
|
384
|
+
handler,
|
|
385
|
+
withFullResponse: true
|
|
386
|
+
}) as any
|
|
387
|
+
})
|
|
248
388
|
}
|
|
249
389
|
}
|
|
250
390
|
|
|
251
|
-
const makeHandlers = <E, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.
|
|
391
|
+
const makeHandlers = <E, Provides, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Any>(
|
|
252
392
|
options: {
|
|
253
|
-
readonly group: HttpApiGroup.HttpApiGroup
|
|
393
|
+
readonly group: HttpApiGroup.HttpApiGroup.Any
|
|
254
394
|
readonly handlers: Chunk.Chunk<Handlers.Item<E, R>>
|
|
255
395
|
}
|
|
256
|
-
): Handlers<E, R, Endpoints> => {
|
|
396
|
+
): Handlers<E, Provides, R, Endpoints> => {
|
|
257
397
|
const self = Object.create(HandlersProto)
|
|
258
398
|
self.group = options.group
|
|
259
399
|
self.handlers = options.handlers
|
|
@@ -261,12 +401,12 @@ const makeHandlers = <E, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Al
|
|
|
261
401
|
}
|
|
262
402
|
|
|
263
403
|
/**
|
|
264
|
-
* Create a `Layer` that will implement all the endpoints in an `
|
|
404
|
+
* Create a `Layer` that will implement all the endpoints in an `HttpApi`.
|
|
265
405
|
*
|
|
266
406
|
* An unimplemented `Handlers` instance is passed to the `build` function, which
|
|
267
407
|
* you can use to add handlers to the group.
|
|
268
408
|
*
|
|
269
|
-
* You can implement endpoints using the `
|
|
409
|
+
* You can implement endpoints using the `handlers.handle` api.
|
|
270
410
|
*
|
|
271
411
|
* @since 1.0.0
|
|
272
412
|
* @category handlers
|
|
@@ -274,146 +414,303 @@ const makeHandlers = <E, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Al
|
|
|
274
414
|
export const group = <
|
|
275
415
|
Groups extends HttpApiGroup.HttpApiGroup.Any,
|
|
276
416
|
ApiError,
|
|
277
|
-
|
|
278
|
-
const Name extends Groups
|
|
279
|
-
|
|
280
|
-
EX = never,
|
|
281
|
-
RX = never
|
|
417
|
+
ApiR,
|
|
418
|
+
const Name extends HttpApiGroup.HttpApiGroup.Name<Groups>,
|
|
419
|
+
Return
|
|
282
420
|
>(
|
|
283
|
-
api: HttpApi.HttpApi<Groups, ApiError,
|
|
421
|
+
api: HttpApi.HttpApi<Groups, ApiError, ApiR>,
|
|
284
422
|
groupName: Name,
|
|
285
423
|
build: (
|
|
286
|
-
handlers: Handlers<
|
|
287
|
-
) =>
|
|
288
|
-
| Handlers<NoInfer<ApiError> | HttpApiGroup.HttpApiGroup.ErrorWithName<Groups, Name>, RH>
|
|
289
|
-
| Effect.Effect<Handlers<NoInfer<ApiError> | HttpApiGroup.HttpApiGroup.ErrorWithName<Groups, Name>, RH>, EX, RX>
|
|
424
|
+
handlers: Handlers.FromGroup<ApiError, ApiR, HttpApiGroup.HttpApiGroup.WithName<Groups, Name>>
|
|
425
|
+
) => Handlers.ValidateReturn<Return>
|
|
290
426
|
): Layer.Layer<
|
|
291
|
-
HttpApiGroup.
|
|
292
|
-
|
|
293
|
-
|
|
427
|
+
HttpApiGroup.Group<Name>,
|
|
428
|
+
Handlers.Error<Return>,
|
|
429
|
+
| Handlers.Context<Return>
|
|
430
|
+
| HttpApiGroup.HttpApiGroup.ContextWithName<Groups, Name>
|
|
294
431
|
> =>
|
|
295
432
|
Router.use((router) =>
|
|
296
433
|
Effect.gen(function*() {
|
|
297
434
|
const context = yield* Effect.context<any>()
|
|
298
|
-
const group =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
435
|
+
const group = HashMap.unsafeGet(api.groups, groupName)
|
|
436
|
+
const result = build(makeHandlers({ group, handlers: Chunk.empty() }))
|
|
437
|
+
const handlers: Handlers<any, any, any> = Effect.isEffect(result)
|
|
438
|
+
? (yield* result as Effect.Effect<any, any, any>)
|
|
439
|
+
: result
|
|
440
|
+
const groupMiddleware = makeMiddlewareMap((group as any).middlewares, context)
|
|
304
441
|
const routes: Array<HttpRouter.Route<any, any>> = []
|
|
305
442
|
for (const item of handlers.handlers) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
},
|
|
319
|
-
item.withFullResponse
|
|
320
|
-
))
|
|
321
|
-
}
|
|
443
|
+
const middleware = makeMiddlewareMap((item as any).endpoint.middlewares, context, groupMiddleware)
|
|
444
|
+
routes.push(handlerToRoute(
|
|
445
|
+
item.endpoint,
|
|
446
|
+
middleware,
|
|
447
|
+
function(request) {
|
|
448
|
+
return Effect.mapInputContext(
|
|
449
|
+
item.handler(request),
|
|
450
|
+
(input) => Context.merge(context, input)
|
|
451
|
+
)
|
|
452
|
+
},
|
|
453
|
+
item.withFullResponse
|
|
454
|
+
))
|
|
322
455
|
}
|
|
323
456
|
yield* router.concat(HttpRouter.fromIterable(routes))
|
|
324
457
|
})
|
|
325
458
|
) as any
|
|
326
459
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
460
|
+
// internal
|
|
461
|
+
|
|
462
|
+
const requestPayload = (
|
|
463
|
+
request: HttpServerRequest.HttpServerRequest,
|
|
464
|
+
urlParams: ReadonlyRecord<string, string | Array<string>>,
|
|
465
|
+
isMultipart: boolean
|
|
466
|
+
): Effect.Effect<
|
|
467
|
+
unknown,
|
|
468
|
+
never,
|
|
469
|
+
| FileSystem
|
|
470
|
+
| Path
|
|
471
|
+
| Scope
|
|
472
|
+
> =>
|
|
473
|
+
HttpMethod.hasBody(request.method)
|
|
474
|
+
? isMultipart
|
|
475
|
+
? Effect.orDie(request.multipart)
|
|
476
|
+
: Effect.orDie(request.json)
|
|
477
|
+
: Effect.succeed(urlParams)
|
|
478
|
+
|
|
479
|
+
type MiddlewareMap = Map<string, {
|
|
480
|
+
readonly tag: HttpApiMiddleware.TagClassAny
|
|
481
|
+
readonly effect: Effect.Effect<any, any, any>
|
|
482
|
+
}>
|
|
483
|
+
|
|
484
|
+
const makeMiddlewareMap = (
|
|
485
|
+
middleware: HashSet.HashSet<HttpApiMiddleware.TagClassAny>,
|
|
486
|
+
context: Context.Context<never>,
|
|
487
|
+
initial?: MiddlewareMap
|
|
488
|
+
): MiddlewareMap => {
|
|
489
|
+
const map = new Map<string, {
|
|
490
|
+
readonly tag: HttpApiMiddleware.TagClassAny
|
|
491
|
+
readonly effect: Effect.Effect<any, any, any>
|
|
492
|
+
}>(initial)
|
|
493
|
+
HashSet.forEach(middleware, (tag) => {
|
|
494
|
+
map.set(tag.key, {
|
|
495
|
+
tag,
|
|
496
|
+
effect: Context.unsafeGet(context, tag as any)
|
|
497
|
+
})
|
|
498
|
+
})
|
|
499
|
+
return map
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const handlerToRoute = (
|
|
503
|
+
endpoint_: HttpApiEndpoint.HttpApiEndpoint.Any,
|
|
504
|
+
middleware: MiddlewareMap,
|
|
505
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, any, any>,
|
|
506
|
+
isFullResponse: boolean
|
|
507
|
+
): HttpRouter.Route<any, any> => {
|
|
508
|
+
const endpoint = endpoint_ as HttpApiEndpoint.HttpApiEndpoint.AnyWithProps
|
|
509
|
+
const decodePath = Option.map(endpoint.pathSchema, Schema.decodeUnknown)
|
|
510
|
+
const isMultipart = endpoint.payloadSchema.pipe(
|
|
511
|
+
Option.map((schema) => HttpApiSchema.getMultipart(schema.ast)),
|
|
512
|
+
Option.getOrElse(() => false)
|
|
513
|
+
)
|
|
514
|
+
const decodePayload = Option.map(endpoint.payloadSchema, Schema.decodeUnknown)
|
|
515
|
+
const decodeHeaders = Option.map(endpoint.headersSchema, Schema.decodeUnknown)
|
|
516
|
+
const decodeUrlParams = Option.map(endpoint.urlParamsSchema, Schema.decodeUnknown)
|
|
517
|
+
const encodeSuccess = Schema.encode(makeSuccessSchema(endpoint.successSchema))
|
|
518
|
+
return HttpRouter.makeRoute(
|
|
519
|
+
endpoint.method,
|
|
520
|
+
endpoint.path,
|
|
521
|
+
applyMiddleware(
|
|
522
|
+
middleware,
|
|
523
|
+
Effect.withFiberRuntime((fiber) => {
|
|
524
|
+
const context = fiber.getFiberRef(FiberRef.currentContext)
|
|
525
|
+
const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest)
|
|
526
|
+
const routeContext = Context.unsafeGet(context, HttpRouter.RouteContext)
|
|
527
|
+
const urlParams = Context.unsafeGet(context, HttpServerRequest.ParsedSearchParams)
|
|
528
|
+
return (
|
|
529
|
+
decodePath._tag === "Some"
|
|
530
|
+
? decodePath.value(routeContext.params)
|
|
531
|
+
: Effect.succeed(routeContext.params)
|
|
532
|
+
).pipe(
|
|
533
|
+
Effect.bindTo("pathParams"),
|
|
534
|
+
decodePayload._tag === "Some"
|
|
535
|
+
? Effect.bind(
|
|
536
|
+
"payload",
|
|
537
|
+
(_) =>
|
|
538
|
+
Effect.flatMap(
|
|
539
|
+
requestPayload(request, urlParams, isMultipart),
|
|
540
|
+
decodePayload.value
|
|
541
|
+
)
|
|
542
|
+
) as typeof identity
|
|
543
|
+
: identity,
|
|
544
|
+
decodeHeaders._tag === "Some"
|
|
545
|
+
? Effect.bind("headers", (_) => decodeHeaders.value(request.headers)) as typeof identity
|
|
546
|
+
: identity,
|
|
547
|
+
decodeUrlParams._tag === "Some"
|
|
548
|
+
? Effect.bind("urlParams", (_) => decodeUrlParams.value(urlParams)) as typeof identity
|
|
549
|
+
: identity,
|
|
550
|
+
Effect.flatMap((input) => {
|
|
551
|
+
const request: any = { path: input.pathParams }
|
|
552
|
+
if ("payload" in input) {
|
|
553
|
+
request.payload = input.payload
|
|
554
|
+
}
|
|
555
|
+
if ("headers" in input) {
|
|
556
|
+
request.headers = input.headers
|
|
557
|
+
}
|
|
558
|
+
if ("urlParams" in input) {
|
|
559
|
+
request.urlParams = input.urlParams
|
|
560
|
+
}
|
|
561
|
+
return handler(request)
|
|
562
|
+
}),
|
|
563
|
+
isFullResponse ?
|
|
564
|
+
identity as (_: any) => Effect.Effect<HttpServerResponse.HttpServerResponse> :
|
|
565
|
+
Effect.flatMap(encodeSuccess),
|
|
566
|
+
Effect.catchIf(ParseResult.isParseError, HttpApiDecodeError.refailParseError)
|
|
567
|
+
)
|
|
568
|
+
})
|
|
569
|
+
)
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const applyMiddleware = <A extends Effect.Effect<any, any, any>>(
|
|
574
|
+
middleware: MiddlewareMap,
|
|
575
|
+
handler: A
|
|
576
|
+
) => {
|
|
577
|
+
for (const entry of middleware.values()) {
|
|
578
|
+
const effect = HttpApiMiddleware.SecurityTypeId in entry.tag ? makeSecurityMiddleware(entry as any) : entry.effect
|
|
579
|
+
if (entry.tag.optional) {
|
|
580
|
+
const previous = handler
|
|
581
|
+
handler = Effect.matchEffect(effect, {
|
|
582
|
+
onFailure: () => previous,
|
|
583
|
+
onSuccess: entry.tag.provides !== undefined
|
|
584
|
+
? (value) => Effect.provideService(previous, entry.tag.provides as any, value)
|
|
585
|
+
: (_) => previous
|
|
586
|
+
}) as any
|
|
587
|
+
} else {
|
|
588
|
+
handler = entry.tag.provides !== undefined
|
|
589
|
+
? Effect.provideServiceEffect(handler, entry.tag.provides as any, effect) as any
|
|
590
|
+
: Effect.zipRight(effect, handler) as any
|
|
359
591
|
}
|
|
360
|
-
): <EG, RG>(self: Handlers<EG, RG, Endpoints>) => Handlers<
|
|
361
|
-
EG | Exclude<E, HttpApiEndpoint.HttpApiEndpoint.ErrorWithName<Endpoints, Name>> | HttpApiDecodeError,
|
|
362
|
-
RG | HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<R>,
|
|
363
|
-
HttpApiEndpoint.HttpApiEndpoint.ExcludeName<Endpoints, Name>
|
|
364
|
-
>
|
|
365
|
-
} = <Endpoints extends HttpApiEndpoint.HttpApiEndpoint.All, const Name extends Endpoints["name"], E, R>(
|
|
366
|
-
name: Name,
|
|
367
|
-
handler: HttpApiEndpoint.HttpApiEndpoint.HandlerWithName<Endpoints, Name, E, R>,
|
|
368
|
-
options?: {
|
|
369
|
-
readonly withFullResponse: true
|
|
370
592
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
593
|
+
return handler
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const securityMiddlewareCache = globalValue<WeakMap<any, Effect.Effect<any, any, any>>>(
|
|
597
|
+
"securityMiddlewareCache",
|
|
598
|
+
() => new WeakMap()
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
const makeSecurityMiddleware = (
|
|
602
|
+
entry: {
|
|
603
|
+
readonly tag: HttpApiMiddleware.TagClassSecurityAny
|
|
604
|
+
readonly effect: Record<string, (_: any) => Effect.Effect<any, any>>
|
|
605
|
+
}
|
|
606
|
+
): Effect.Effect<any, any, any> => {
|
|
607
|
+
if (securityMiddlewareCache.has(entry.tag)) {
|
|
608
|
+
return securityMiddlewareCache.get(entry.tag)!
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
let effect: Effect.Effect<any, any, any> | undefined
|
|
612
|
+
for (const [key, security] of Object.entries(entry.tag.security)) {
|
|
613
|
+
const decode = securityDecode(security)
|
|
614
|
+
const handler = entry.effect[key]
|
|
615
|
+
const middleware = Effect.flatMap(decode, handler)
|
|
616
|
+
effect = effect === undefined ? middleware : Effect.catchAll(effect, () => middleware)
|
|
617
|
+
}
|
|
618
|
+
if (effect === undefined) {
|
|
619
|
+
effect = Effect.void
|
|
382
620
|
}
|
|
383
|
-
|
|
384
|
-
return
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
621
|
+
securityMiddlewareCache.set(entry.tag, effect)
|
|
622
|
+
return effect
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const responseSchema = Schema.declare(HttpServerResponse.isServerResponse)
|
|
626
|
+
|
|
627
|
+
const makeSuccessSchema = (
|
|
628
|
+
schema: Schema.Schema.Any
|
|
629
|
+
): Schema.Schema<unknown, HttpServerResponse.HttpServerResponse> => {
|
|
630
|
+
const schemas = new Set<Schema.Schema.Any>()
|
|
631
|
+
HttpApiSchema.deunionize(schemas, schema)
|
|
632
|
+
return Schema.Union(...Array.from(schemas, toResponseSuccess)) as any
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const makeErrorSchema = (
|
|
636
|
+
api: HttpApi.HttpApi.AnyWithProps
|
|
637
|
+
): Schema.Schema<unknown, HttpServerResponse.HttpServerResponse> => {
|
|
638
|
+
const schemas = new Set<Schema.Schema.Any>()
|
|
639
|
+
HttpApiSchema.deunionize(schemas, api.errorSchema)
|
|
640
|
+
HashMap.forEach(api.groups, (group) => {
|
|
641
|
+
HashMap.forEach(group.endpoints, (endpoint) => {
|
|
642
|
+
HttpApiSchema.deunionize(schemas, endpoint.errorSchema)
|
|
643
|
+
})
|
|
644
|
+
HttpApiSchema.deunionize(schemas, group.errorSchema)
|
|
392
645
|
})
|
|
646
|
+
return Schema.Union(...Array.from(schemas, toResponseError)) as any
|
|
393
647
|
}
|
|
394
648
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
649
|
+
const decodeForbidden = <A>(_: A, __: AST.ParseOptions, ast: AST.Transformation) =>
|
|
650
|
+
ParseResult.fail(new ParseResult.Forbidden(ast, _, "Encode only schema"))
|
|
651
|
+
|
|
652
|
+
const toResponseSchema = (getStatus: (ast: AST.AST) => number) => {
|
|
653
|
+
const cache = new WeakMap<AST.AST, Schema.Schema.All>()
|
|
654
|
+
const schemaToResponse = (
|
|
655
|
+
data: any,
|
|
656
|
+
_: AST.ParseOptions,
|
|
657
|
+
ast: AST.Transformation
|
|
658
|
+
): Effect.Effect<HttpServerResponse.HttpServerResponse, ParseResult.ParseIssue> => {
|
|
659
|
+
const isEmpty = HttpApiSchema.isVoid(ast.to)
|
|
660
|
+
const status = getStatus(ast.to)
|
|
661
|
+
if (isEmpty) {
|
|
662
|
+
return HttpServerResponse.empty({ status })
|
|
663
|
+
}
|
|
664
|
+
const encoding = HttpApiSchema.getEncoding(ast.to)
|
|
665
|
+
switch (encoding.kind) {
|
|
666
|
+
case "Json": {
|
|
667
|
+
return Effect.mapError(
|
|
668
|
+
HttpServerResponse.json(data, {
|
|
669
|
+
status,
|
|
670
|
+
contentType: encoding.contentType
|
|
671
|
+
}),
|
|
672
|
+
(error) => new ParseResult.Type(ast, error, "Could not encode to JSON")
|
|
673
|
+
)
|
|
674
|
+
}
|
|
675
|
+
case "Text": {
|
|
676
|
+
return ParseResult.succeed(HttpServerResponse.text(data as any, {
|
|
677
|
+
status,
|
|
678
|
+
contentType: encoding.contentType
|
|
679
|
+
}))
|
|
680
|
+
}
|
|
681
|
+
case "Uint8Array": {
|
|
682
|
+
return ParseResult.succeed(HttpServerResponse.uint8Array(data as any, {
|
|
683
|
+
status,
|
|
684
|
+
contentType: encoding.contentType
|
|
685
|
+
}))
|
|
686
|
+
}
|
|
687
|
+
case "UrlParams": {
|
|
688
|
+
return ParseResult.succeed(HttpServerResponse.urlParams(data as any, {
|
|
689
|
+
status,
|
|
690
|
+
contentType: encoding.contentType
|
|
691
|
+
}))
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return <A, I, R>(schema: Schema.Schema<A, I, R>): Schema.Schema<A, HttpServerResponse.HttpServerResponse, R> => {
|
|
696
|
+
if (cache.has(schema.ast)) {
|
|
697
|
+
return cache.get(schema.ast)! as any
|
|
698
|
+
}
|
|
699
|
+
const transform = Schema.transformOrFail(responseSchema, schema, {
|
|
700
|
+
decode: decodeForbidden,
|
|
701
|
+
encode: schemaToResponse
|
|
416
702
|
})
|
|
703
|
+
cache.set(transform.ast, transform)
|
|
704
|
+
return transform
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const toResponseSuccess = toResponseSchema(HttpApiSchema.getStatusSuccessAST)
|
|
709
|
+
const toResponseError = toResponseSchema(HttpApiSchema.getStatusErrorAST)
|
|
710
|
+
|
|
711
|
+
// ----------------------------------------------------------------------------
|
|
712
|
+
// Global middleware
|
|
713
|
+
// ----------------------------------------------------------------------------
|
|
417
714
|
|
|
418
715
|
/**
|
|
419
716
|
* @since 1.0.0
|
|
@@ -426,17 +723,11 @@ export class Middleware extends Context.Tag("@effect/platform/HttpApiBuilder/Mid
|
|
|
426
723
|
|
|
427
724
|
/**
|
|
428
725
|
* @since 1.0.0
|
|
429
|
-
* @category
|
|
726
|
+
* @category global
|
|
430
727
|
*/
|
|
431
|
-
export
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
* @category middleware
|
|
435
|
-
*/
|
|
436
|
-
export type Fn<Error, R = HttpRouter.HttpRouter.Provided> = (
|
|
437
|
-
httpApp: HttpApp.Default
|
|
438
|
-
) => HttpApp.Default<Error, R>
|
|
439
|
-
}
|
|
728
|
+
export type MiddlewareFn<Error, R = HttpRouter.HttpRouter.Provided> = (
|
|
729
|
+
httpApp: HttpApp.Default
|
|
730
|
+
) => HttpApp.Default<Error, R>
|
|
440
731
|
|
|
441
732
|
const middlewareAdd = (middleware: HttpMiddleware.HttpMiddleware): Effect.Effect<HttpMiddleware.HttpMiddleware> =>
|
|
442
733
|
Effect.map(
|
|
@@ -465,7 +756,7 @@ const middlewareAddNoContext = (
|
|
|
465
756
|
* @since 1.0.0
|
|
466
757
|
* @category middleware
|
|
467
758
|
*/
|
|
468
|
-
export const
|
|
759
|
+
export const middleware: {
|
|
469
760
|
/**
|
|
470
761
|
* Create an `HttpApi` level middleware `Layer`.
|
|
471
762
|
*
|
|
@@ -473,7 +764,7 @@ export const middlewareLayer: {
|
|
|
473
764
|
* @category middleware
|
|
474
765
|
*/
|
|
475
766
|
<EX = never, RX = never>(
|
|
476
|
-
middleware:
|
|
767
|
+
middleware: MiddlewareFn<never> | Effect.Effect<MiddlewareFn<never>, EX, RX>,
|
|
477
768
|
options?: {
|
|
478
769
|
readonly withContext?: false | undefined
|
|
479
770
|
}
|
|
@@ -485,7 +776,7 @@ export const middlewareLayer: {
|
|
|
485
776
|
* @category middleware
|
|
486
777
|
*/
|
|
487
778
|
<R, EX = never, RX = never>(
|
|
488
|
-
middleware:
|
|
779
|
+
middleware: MiddlewareFn<never, R> | Effect.Effect<MiddlewareFn<never, R>, EX, RX>,
|
|
489
780
|
options: {
|
|
490
781
|
readonly withContext: true
|
|
491
782
|
}
|
|
@@ -498,7 +789,7 @@ export const middlewareLayer: {
|
|
|
498
789
|
*/
|
|
499
790
|
<Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR, EX = never, RX = never>(
|
|
500
791
|
api: HttpApi.HttpApi<Groups, Error, ErrorR>,
|
|
501
|
-
middleware:
|
|
792
|
+
middleware: MiddlewareFn<NoInfer<Error>> | Effect.Effect<MiddlewareFn<NoInfer<Error>>, EX, RX>,
|
|
502
793
|
options?: {
|
|
503
794
|
readonly withContext?: false | undefined
|
|
504
795
|
}
|
|
@@ -511,20 +802,20 @@ export const middlewareLayer: {
|
|
|
511
802
|
*/
|
|
512
803
|
<Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR, R, EX = never, RX = never>(
|
|
513
804
|
api: HttpApi.HttpApi<Groups, Error, ErrorR>,
|
|
514
|
-
middleware:
|
|
805
|
+
middleware: MiddlewareFn<NoInfer<Error>, R> | Effect.Effect<MiddlewareFn<NoInfer<Error>, R>, EX, RX>,
|
|
515
806
|
options: {
|
|
516
807
|
readonly withContext: true
|
|
517
808
|
}
|
|
518
809
|
): Layer.Layer<never, EX, HttpRouter.HttpRouter.ExcludeProvided<R> | RX>
|
|
519
810
|
} = (
|
|
520
811
|
...args: [
|
|
521
|
-
middleware:
|
|
812
|
+
middleware: MiddlewareFn<any, any> | Effect.Effect<MiddlewareFn<any, any>, any, any>,
|
|
522
813
|
options?: {
|
|
523
814
|
readonly withContext?: boolean | undefined
|
|
524
815
|
} | undefined
|
|
525
816
|
] | [
|
|
526
817
|
api: HttpApi.HttpApi.Any,
|
|
527
|
-
middleware:
|
|
818
|
+
middleware: MiddlewareFn<any, any> | Effect.Effect<MiddlewareFn<any, any>, any, any>,
|
|
528
819
|
options?: {
|
|
529
820
|
readonly withContext?: boolean | undefined
|
|
530
821
|
} | undefined
|
|
@@ -546,7 +837,7 @@ export const middlewareLayer: {
|
|
|
546
837
|
* @since 1.0.0
|
|
547
838
|
* @category middleware
|
|
548
839
|
*/
|
|
549
|
-
export const
|
|
840
|
+
export const middlewareScoped: {
|
|
550
841
|
/**
|
|
551
842
|
* Create an `HttpApi` level middleware `Layer`, that has a `Scope` provided to
|
|
552
843
|
* the constructor.
|
|
@@ -555,7 +846,7 @@ export const middlewareLayerScoped: {
|
|
|
555
846
|
* @category middleware
|
|
556
847
|
*/
|
|
557
848
|
<EX, RX>(
|
|
558
|
-
middleware: Effect.Effect<
|
|
849
|
+
middleware: Effect.Effect<MiddlewareFn<never>, EX, RX>,
|
|
559
850
|
options?: {
|
|
560
851
|
readonly withContext?: false | undefined
|
|
561
852
|
}
|
|
@@ -568,7 +859,7 @@ export const middlewareLayerScoped: {
|
|
|
568
859
|
* @category middleware
|
|
569
860
|
*/
|
|
570
861
|
<R, EX, RX>(
|
|
571
|
-
middleware: Effect.Effect<
|
|
862
|
+
middleware: Effect.Effect<MiddlewareFn<never, R>, EX, RX>,
|
|
572
863
|
options: {
|
|
573
864
|
readonly withContext: true
|
|
574
865
|
}
|
|
@@ -582,7 +873,7 @@ export const middlewareLayerScoped: {
|
|
|
582
873
|
*/
|
|
583
874
|
<Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR, EX, RX>(
|
|
584
875
|
api: HttpApi.HttpApi<Groups, Error, ErrorR>,
|
|
585
|
-
middleware: Effect.Effect<
|
|
876
|
+
middleware: Effect.Effect<MiddlewareFn<NoInfer<Error>>, EX, RX>,
|
|
586
877
|
options?: {
|
|
587
878
|
readonly withContext?: false | undefined
|
|
588
879
|
}
|
|
@@ -596,20 +887,20 @@ export const middlewareLayerScoped: {
|
|
|
596
887
|
*/
|
|
597
888
|
<Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR, R, EX, RX>(
|
|
598
889
|
api: HttpApi.HttpApi<Groups, Error, ErrorR>,
|
|
599
|
-
middleware: Effect.Effect<
|
|
890
|
+
middleware: Effect.Effect<MiddlewareFn<NoInfer<Error>, R>, EX, RX>,
|
|
600
891
|
options: {
|
|
601
892
|
readonly withContext: true
|
|
602
893
|
}
|
|
603
894
|
): Layer.Layer<never, EX, HttpRouter.HttpRouter.ExcludeProvided<R> | Exclude<RX, Scope>>
|
|
604
895
|
} = (
|
|
605
896
|
...args: [
|
|
606
|
-
middleware:
|
|
897
|
+
middleware: MiddlewareFn<any, any> | Effect.Effect<MiddlewareFn<any, any>, any, any>,
|
|
607
898
|
options?: {
|
|
608
899
|
readonly withContext?: boolean | undefined
|
|
609
900
|
} | undefined
|
|
610
901
|
] | [
|
|
611
902
|
api: HttpApi.HttpApi.Any,
|
|
612
|
-
middleware:
|
|
903
|
+
middleware: MiddlewareFn<any, any> | Effect.Effect<MiddlewareFn<any, any>, any, any>,
|
|
613
904
|
options?: {
|
|
614
905
|
readonly withContext?: boolean | undefined
|
|
615
906
|
} | undefined
|
|
@@ -637,7 +928,7 @@ export const middlewareCors = (
|
|
|
637
928
|
readonly maxAge?: number | undefined
|
|
638
929
|
readonly credentials?: boolean | undefined
|
|
639
930
|
} | undefined
|
|
640
|
-
): Layer.Layer<never> =>
|
|
931
|
+
): Layer.Layer<never> => middleware(HttpMiddleware.cors(options))
|
|
641
932
|
|
|
642
933
|
/**
|
|
643
934
|
* A middleware that adds an openapi.json endpoint to the API.
|
|
@@ -649,10 +940,10 @@ export const middlewareOpenApi = (
|
|
|
649
940
|
options?: {
|
|
650
941
|
readonly path?: HttpRouter.PathInput | undefined
|
|
651
942
|
} | undefined
|
|
652
|
-
): Layer.Layer<never, never, HttpApi.
|
|
943
|
+
): Layer.Layer<never, never, HttpApi.Api> =>
|
|
653
944
|
Router.use((router) =>
|
|
654
945
|
Effect.gen(function*() {
|
|
655
|
-
const api = yield* HttpApi.
|
|
946
|
+
const { api } = yield* HttpApi.Api
|
|
656
947
|
const spec = OpenApi.fromApi(api)
|
|
657
948
|
const response = yield* HttpServerResponse.json(spec).pipe(
|
|
658
949
|
Effect.orDie
|
|
@@ -661,21 +952,11 @@ export const middlewareOpenApi = (
|
|
|
661
952
|
})
|
|
662
953
|
)
|
|
663
954
|
|
|
664
|
-
/**
|
|
665
|
-
* @since 1.0.0
|
|
666
|
-
* @category middleware
|
|
667
|
-
*/
|
|
668
|
-
export interface SecurityMiddleware<I, EM = never, RM = never> {
|
|
669
|
-
<Endpoints extends HttpApiEndpoint.HttpApiEndpoint.All, E, R>(
|
|
670
|
-
self: Handlers<E, R, Endpoints>
|
|
671
|
-
): Handlers<E | EM, Exclude<R, I> | HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<RM>, Endpoints>
|
|
672
|
-
}
|
|
673
|
-
|
|
674
955
|
const bearerLen = `Bearer `.length
|
|
675
956
|
|
|
676
957
|
/**
|
|
677
958
|
* @since 1.0.0
|
|
678
|
-
* @category
|
|
959
|
+
* @category security
|
|
679
960
|
*/
|
|
680
961
|
export const securityDecode = <Security extends HttpApiSecurity.HttpApiSecurity>(
|
|
681
962
|
self: Security
|
|
@@ -738,9 +1019,9 @@ export const securityDecode = <Security extends HttpApiSecurity.HttpApiSecurity>
|
|
|
738
1019
|
* You can use this api before returning a response from an endpoint handler.
|
|
739
1020
|
*
|
|
740
1021
|
* ```ts
|
|
741
|
-
*
|
|
1022
|
+
* handlers.handle(
|
|
742
1023
|
* "authenticate",
|
|
743
|
-
* (_) =>
|
|
1024
|
+
* (_) => HttpApiBuilder.securitySetCookie(security, "secret123")
|
|
744
1025
|
* )
|
|
745
1026
|
* ```
|
|
746
1027
|
*
|
|
@@ -763,233 +1044,3 @@ export const securitySetCookie = (
|
|
|
763
1044
|
)
|
|
764
1045
|
)
|
|
765
1046
|
}
|
|
766
|
-
|
|
767
|
-
/**
|
|
768
|
-
* Make a middleware from an `HttpApiSecurity` instance, that can be used when
|
|
769
|
-
* constructing a `Handlers` group.
|
|
770
|
-
*
|
|
771
|
-
* @since 1.0.0
|
|
772
|
-
* @category middleware
|
|
773
|
-
* @example
|
|
774
|
-
* import { HttpApiBuilder, HttpApiSecurity } from "@effect/platform"
|
|
775
|
-
* import { Schema } from "@effect/schema"
|
|
776
|
-
* import { Context, Effect, Redacted } from "effect"
|
|
777
|
-
*
|
|
778
|
-
* class User extends Schema.Class<User>("User")({
|
|
779
|
-
* id: Schema.Number
|
|
780
|
-
* }) {}
|
|
781
|
-
*
|
|
782
|
-
* class CurrentUser extends Context.Tag("CurrentUser")<CurrentUser, User>() {}
|
|
783
|
-
*
|
|
784
|
-
* class Accounts extends Context.Tag("Accounts")<Accounts, {
|
|
785
|
-
* readonly findUserByAccessToken: (accessToken: string) => Effect.Effect<User>
|
|
786
|
-
* }>() {}
|
|
787
|
-
*
|
|
788
|
-
* const securityMiddleware = Effect.gen(function*() {
|
|
789
|
-
* const accounts = yield* Accounts
|
|
790
|
-
* return HttpApiBuilder.middlewareSecurity(
|
|
791
|
-
* HttpApiSecurity.bearer,
|
|
792
|
-
* CurrentUser,
|
|
793
|
-
* (token) => accounts.findUserByAccessToken(Redacted.value(token))
|
|
794
|
-
* )
|
|
795
|
-
* })
|
|
796
|
-
*/
|
|
797
|
-
export const middlewareSecurity = <Security extends HttpApiSecurity.HttpApiSecurity, I, S, EM, RM>(
|
|
798
|
-
self: Security,
|
|
799
|
-
tag: Context.Tag<I, S>,
|
|
800
|
-
f: (
|
|
801
|
-
credentials: HttpApiSecurity.HttpApiSecurity.Type<Security>
|
|
802
|
-
) => Effect.Effect<S, EM, RM>
|
|
803
|
-
): SecurityMiddleware<I, EM, RM> =>
|
|
804
|
-
middleware(Effect.provideServiceEffect(
|
|
805
|
-
tag,
|
|
806
|
-
Effect.flatMap(securityDecode(self), f)
|
|
807
|
-
)) as SecurityMiddleware<I, EM, RM>
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Make a middleware from an `HttpApiSecurity` instance, that can be used when
|
|
811
|
-
* constructing a `Handlers` group.
|
|
812
|
-
*
|
|
813
|
-
* This version does not supply any context to the handlers.
|
|
814
|
-
*
|
|
815
|
-
* @since 1.0.0
|
|
816
|
-
* @category middleware
|
|
817
|
-
*/
|
|
818
|
-
export const middlewareSecurityVoid = <Security extends HttpApiSecurity.HttpApiSecurity, X, EM, RM>(
|
|
819
|
-
self: Security,
|
|
820
|
-
f: (
|
|
821
|
-
credentials: HttpApiSecurity.HttpApiSecurity.Type<Security>
|
|
822
|
-
) => Effect.Effect<X, EM, RM>
|
|
823
|
-
): SecurityMiddleware<never, EM, RM> =>
|
|
824
|
-
middleware((httpApp) =>
|
|
825
|
-
securityDecode(self).pipe(
|
|
826
|
-
Effect.flatMap(f),
|
|
827
|
-
Effect.zipRight(httpApp)
|
|
828
|
-
)
|
|
829
|
-
) as SecurityMiddleware<never, EM, RM>
|
|
830
|
-
|
|
831
|
-
// internal
|
|
832
|
-
|
|
833
|
-
const requestPayload = (
|
|
834
|
-
request: HttpServerRequest.HttpServerRequest,
|
|
835
|
-
urlParams: ReadonlyRecord<string, string | Array<string>>,
|
|
836
|
-
isMultipart: boolean
|
|
837
|
-
): Effect.Effect<
|
|
838
|
-
unknown,
|
|
839
|
-
never,
|
|
840
|
-
| FileSystem
|
|
841
|
-
| Path
|
|
842
|
-
| Scope
|
|
843
|
-
> =>
|
|
844
|
-
HttpMethod.hasBody(request.method)
|
|
845
|
-
? isMultipart
|
|
846
|
-
? Effect.orDie(request.multipart)
|
|
847
|
-
: Effect.orDie(request.json)
|
|
848
|
-
: Effect.succeed(urlParams)
|
|
849
|
-
|
|
850
|
-
const handlerToRoute = (
|
|
851
|
-
endpoint: HttpApiEndpoint.HttpApiEndpoint.Any,
|
|
852
|
-
handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, any, any>,
|
|
853
|
-
isFullResponse: boolean
|
|
854
|
-
): HttpRouter.Route<any, any> => {
|
|
855
|
-
const decodePath = Option.map(endpoint.pathSchema, Schema.decodeUnknown)
|
|
856
|
-
const isMultipart = endpoint.payloadSchema.pipe(
|
|
857
|
-
Option.map((schema) => HttpApiSchema.getMultipart(schema.ast)),
|
|
858
|
-
Option.getOrElse(() => false)
|
|
859
|
-
)
|
|
860
|
-
const decodePayload = Option.map(endpoint.payloadSchema, Schema.decodeUnknown)
|
|
861
|
-
const decodeHeaders = Option.map(endpoint.headersSchema, Schema.decodeUnknown)
|
|
862
|
-
const encoding = HttpApiSchema.getEncoding(endpoint.successSchema.ast)
|
|
863
|
-
const successStatus = HttpApiSchema.getStatusSuccess(endpoint.successSchema)
|
|
864
|
-
const encodeSuccess = Option.map(HttpApiEndpoint.schemaSuccess(endpoint), (schema) => {
|
|
865
|
-
const encode = Schema.encodeUnknown(schema)
|
|
866
|
-
switch (encoding.kind) {
|
|
867
|
-
case "Json": {
|
|
868
|
-
return (body: unknown) =>
|
|
869
|
-
Effect.orDie(
|
|
870
|
-
Effect.flatMap(encode(body), (json) =>
|
|
871
|
-
HttpServerResponse.json(json, {
|
|
872
|
-
status: successStatus,
|
|
873
|
-
contentType: encoding.contentType
|
|
874
|
-
}))
|
|
875
|
-
)
|
|
876
|
-
}
|
|
877
|
-
case "Text": {
|
|
878
|
-
return (body: unknown) =>
|
|
879
|
-
Effect.map(Effect.orDie(encode(body)), (text) =>
|
|
880
|
-
HttpServerResponse.text(text as any, {
|
|
881
|
-
status: successStatus,
|
|
882
|
-
contentType: encoding.contentType
|
|
883
|
-
}))
|
|
884
|
-
}
|
|
885
|
-
case "Uint8Array": {
|
|
886
|
-
return (body: unknown) =>
|
|
887
|
-
Effect.map(Effect.orDie(encode(body)), (data) =>
|
|
888
|
-
HttpServerResponse.uint8Array(data as any, {
|
|
889
|
-
status: successStatus,
|
|
890
|
-
contentType: encoding.contentType
|
|
891
|
-
}))
|
|
892
|
-
}
|
|
893
|
-
case "UrlParams": {
|
|
894
|
-
return (body: unknown) =>
|
|
895
|
-
Effect.map(Effect.orDie(encode(body)), (params) =>
|
|
896
|
-
HttpServerResponse.urlParams(params as any, {
|
|
897
|
-
status: successStatus,
|
|
898
|
-
contentType: encoding.contentType
|
|
899
|
-
}))
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
})
|
|
903
|
-
return HttpRouter.makeRoute(
|
|
904
|
-
endpoint.method,
|
|
905
|
-
endpoint.path,
|
|
906
|
-
Effect.withFiberRuntime((fiber) => {
|
|
907
|
-
const context = fiber.getFiberRef(FiberRef.currentContext)
|
|
908
|
-
const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest)
|
|
909
|
-
const routeContext = Context.unsafeGet(context, HttpRouter.RouteContext)
|
|
910
|
-
const urlParams = Context.unsafeGet(context, HttpServerRequest.ParsedSearchParams)
|
|
911
|
-
return (decodePath._tag === "Some"
|
|
912
|
-
? Effect.catchAll(decodePath.value(routeContext.params), HttpApiDecodeError.refailParseError)
|
|
913
|
-
: Effect.succeed(routeContext.params)).pipe(
|
|
914
|
-
Effect.bindTo("pathParams"),
|
|
915
|
-
decodePayload._tag === "Some"
|
|
916
|
-
? Effect.bind("payload", (_) =>
|
|
917
|
-
requestPayload(request, urlParams, isMultipart).pipe(
|
|
918
|
-
Effect.orDie,
|
|
919
|
-
Effect.flatMap((raw) => Effect.catchAll(decodePayload.value(raw), HttpApiDecodeError.refailParseError))
|
|
920
|
-
))
|
|
921
|
-
: identity,
|
|
922
|
-
decodeHeaders._tag === "Some"
|
|
923
|
-
? Effect.bind("headers", (_) => Effect.orDie(decodeHeaders.value(request.headers)))
|
|
924
|
-
: identity,
|
|
925
|
-
Effect.flatMap((input) => {
|
|
926
|
-
const request: any = { path: input.pathParams }
|
|
927
|
-
if ("payload" in input) {
|
|
928
|
-
request.payload = input.payload
|
|
929
|
-
}
|
|
930
|
-
if ("headers" in input) {
|
|
931
|
-
request.headers = input.headers
|
|
932
|
-
}
|
|
933
|
-
return handler(request)
|
|
934
|
-
}),
|
|
935
|
-
isFullResponse ?
|
|
936
|
-
identity as (_: any) => Effect.Effect<HttpServerResponse.HttpServerResponse> :
|
|
937
|
-
encodeSuccess._tag === "Some"
|
|
938
|
-
? Effect.flatMap(encodeSuccess.value)
|
|
939
|
-
: Effect.as(HttpServerResponse.empty({ status: successStatus }))
|
|
940
|
-
)
|
|
941
|
-
})
|
|
942
|
-
)
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
const astCache = globalValue(
|
|
946
|
-
"@effect/platform/HttpApiBuilder/astCache",
|
|
947
|
-
() => new WeakMap<AST.AST, Schema.Schema.Any>()
|
|
948
|
-
)
|
|
949
|
-
|
|
950
|
-
const makeErrorSchema = (
|
|
951
|
-
api: HttpApi.HttpApi<HttpApiGroup.HttpApiGroup<string, HttpApiEndpoint.HttpApiEndpoint.Any>, any, any>
|
|
952
|
-
): Schema.Schema<unknown, HttpServerResponse.HttpServerResponse> => {
|
|
953
|
-
const schemas = new Set<Schema.Schema.Any>()
|
|
954
|
-
function processSchema(schema: Schema.Schema.Any): void {
|
|
955
|
-
if (astCache.has(schema.ast)) {
|
|
956
|
-
schemas.add(astCache.get(schema.ast)!)
|
|
957
|
-
return
|
|
958
|
-
}
|
|
959
|
-
const ast = schema.ast
|
|
960
|
-
if (ast._tag === "Union") {
|
|
961
|
-
for (const astType of ast.types) {
|
|
962
|
-
const errorSchema = Schema.make(astType).annotations({
|
|
963
|
-
...ast.annotations,
|
|
964
|
-
...astType.annotations
|
|
965
|
-
})
|
|
966
|
-
astCache.set(astType, errorSchema)
|
|
967
|
-
schemas.add(errorSchema)
|
|
968
|
-
}
|
|
969
|
-
} else {
|
|
970
|
-
astCache.set(ast, schema)
|
|
971
|
-
schemas.add(schema)
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
processSchema(api.errorSchema)
|
|
975
|
-
for (const group of api.groups) {
|
|
976
|
-
for (const endpoint of group.endpoints) {
|
|
977
|
-
processSchema(endpoint.errorSchema)
|
|
978
|
-
}
|
|
979
|
-
processSchema(group.errorSchema)
|
|
980
|
-
}
|
|
981
|
-
return Schema.Union(...[...schemas].map((schema) => {
|
|
982
|
-
const status = HttpApiSchema.getStatusError(schema)
|
|
983
|
-
const encoded = AST.encodedAST(schema.ast)
|
|
984
|
-
const isEmpty = encoded._tag === "VoidKeyword"
|
|
985
|
-
return Schema.transformOrFail(Schema.Any, schema, {
|
|
986
|
-
decode: (_, __, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, _, "Encode only schema")),
|
|
987
|
-
encode: (error, _, ast) =>
|
|
988
|
-
isEmpty ?
|
|
989
|
-
HttpServerResponse.empty({ status }) :
|
|
990
|
-
HttpServerResponse.json(error, { status }).pipe(
|
|
991
|
-
Effect.mapError((error) => new ParseResult.Type(ast, error, "Could not encode to JSON"))
|
|
992
|
-
)
|
|
993
|
-
})
|
|
994
|
-
})) as any
|
|
995
|
-
}
|