@effect/platform 0.68.6 → 0.69.1
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 +245 -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 +86 -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 +228 -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 +238 -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 +534 -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 +317 -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,62 @@ 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
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* ).pipe(
|
|
147
|
-
* Layer.provideMerge(NodeContext.layer)
|
|
139
|
+
* MyApiLive,
|
|
140
|
+
* // you could also use NodeHttpServer.layerContext, depending on your
|
|
141
|
+
* // server's platform
|
|
142
|
+
* HttpServer.layerContext
|
|
148
143
|
* )
|
|
149
144
|
* )
|
|
150
|
-
*
|
|
151
|
-
* const handler = HttpApiBuilder.toWebHandler(runtime, HttpMiddleware.logger)
|
|
152
145
|
*/
|
|
153
|
-
export const toWebHandler = <
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
)
|
|
146
|
+
export const toWebHandler = <LA, LE>(
|
|
147
|
+
layer: Layer.Layer<LA | HttpApi.Api | HttpRouter.HttpRouter.DefaultServices, LE>,
|
|
148
|
+
options?: {
|
|
149
|
+
readonly middleware?: (
|
|
150
|
+
httpApp: HttpApp.Default
|
|
151
|
+
) => HttpApp.Default<
|
|
152
|
+
never,
|
|
153
|
+
HttpApi.Api | Router | HttpRouter.HttpRouter.DefaultServices
|
|
154
|
+
>
|
|
155
|
+
readonly memoMap?: Layer.MemoMap
|
|
156
|
+
}
|
|
157
|
+
): {
|
|
158
|
+
readonly handler: (request: Request) => Promise<Response>
|
|
159
|
+
readonly dispose: () => Promise<void>
|
|
160
|
+
} => {
|
|
161
|
+
const runtime = ManagedRuntime.make(
|
|
162
|
+
Layer.merge(layer, Router.Live),
|
|
163
|
+
options?.memoMap
|
|
164
|
+
)
|
|
165
|
+
let handlerCached: ((request: Request) => Promise<Response>) | undefined
|
|
159
166
|
const handlerPromise = httpApp.pipe(
|
|
160
167
|
Effect.bindTo("httpApp"),
|
|
161
168
|
Effect.bind("runtime", () => runtime.runtimeEffect),
|
|
162
169
|
Effect.map(({ httpApp, runtime }) =>
|
|
163
|
-
HttpApp.toWebHandlerRuntime(runtime)(middleware ? middleware(httpApp as any) : httpApp)
|
|
170
|
+
HttpApp.toWebHandlerRuntime(runtime)(options?.middleware ? options.middleware(httpApp as any) as any : httpApp)
|
|
164
171
|
),
|
|
172
|
+
Effect.tap((handler) => {
|
|
173
|
+
handlerCached = handler
|
|
174
|
+
}),
|
|
165
175
|
runtime.runPromise
|
|
166
176
|
)
|
|
167
|
-
|
|
177
|
+
function handler(request: Request): Promise<Response> {
|
|
178
|
+
if (handlerCached !== undefined) {
|
|
179
|
+
return handlerCached(request)
|
|
180
|
+
}
|
|
181
|
+
return handlerPromise.then((handler) => handler(request))
|
|
182
|
+
}
|
|
183
|
+
return { handler, dispose: runtime.dispose } as const
|
|
168
184
|
}
|
|
169
185
|
|
|
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
186
|
/**
|
|
187
187
|
* @since 1.0.0
|
|
188
188
|
* @category handlers
|
|
@@ -196,21 +196,49 @@ export const HandlersTypeId: unique symbol = Symbol.for("@effect/platform/HttpAp
|
|
|
196
196
|
export type HandlersTypeId = typeof HandlersTypeId
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
|
-
* Represents a handled
|
|
199
|
+
* Represents a handled `HttpApi`.
|
|
200
200
|
*
|
|
201
201
|
* @since 1.0.0
|
|
202
202
|
* @category handlers
|
|
203
203
|
*/
|
|
204
204
|
export interface Handlers<
|
|
205
205
|
E,
|
|
206
|
+
Provides,
|
|
206
207
|
R,
|
|
207
|
-
Endpoints extends HttpApiEndpoint.HttpApiEndpoint.
|
|
208
|
+
Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Any = never
|
|
208
209
|
> extends Pipeable {
|
|
209
210
|
readonly [HandlersTypeId]: {
|
|
210
211
|
_Endpoints: Covariant<Endpoints>
|
|
211
212
|
}
|
|
212
|
-
readonly group: HttpApiGroup.HttpApiGroup
|
|
213
|
+
readonly group: HttpApiGroup.HttpApiGroup.AnyWithProps
|
|
213
214
|
readonly handlers: Chunk.Chunk<Handlers.Item<E, R>>
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
|
|
218
|
+
*/
|
|
219
|
+
handle<Name extends HttpApiEndpoint.HttpApiEndpoint.Name<Endpoints>, R1>(
|
|
220
|
+
name: Name,
|
|
221
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.HandlerWithName<Endpoints, Name, E, R1>
|
|
222
|
+
): Handlers<
|
|
223
|
+
E,
|
|
224
|
+
Provides,
|
|
225
|
+
R | Exclude<HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<Endpoints, Name, R1>, Provides>,
|
|
226
|
+
HttpApiEndpoint.HttpApiEndpoint.ExcludeName<Endpoints, Name>
|
|
227
|
+
>
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
|
|
231
|
+
* This version of the api allows you to return the full response object.
|
|
232
|
+
*/
|
|
233
|
+
handleRaw<Name extends HttpApiEndpoint.HttpApiEndpoint.Name<Endpoints>, R1>(
|
|
234
|
+
name: Name,
|
|
235
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.HandlerResponseWithName<Endpoints, Name, E, R1>
|
|
236
|
+
): Handlers<
|
|
237
|
+
E,
|
|
238
|
+
Provides,
|
|
239
|
+
R | Exclude<HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<Endpoints, Name, R1>, Provides>,
|
|
240
|
+
HttpApiEndpoint.HttpApiEndpoint.ExcludeName<Endpoints, Name>
|
|
241
|
+
>
|
|
214
242
|
}
|
|
215
243
|
|
|
216
244
|
/**
|
|
@@ -218,6 +246,14 @@ export interface Handlers<
|
|
|
218
246
|
* @category handlers
|
|
219
247
|
*/
|
|
220
248
|
export declare namespace Handlers {
|
|
249
|
+
/**
|
|
250
|
+
* @since 1.0.0
|
|
251
|
+
* @category handlers
|
|
252
|
+
*/
|
|
253
|
+
export interface Any {
|
|
254
|
+
readonly [HandlersTypeId]: any
|
|
255
|
+
}
|
|
256
|
+
|
|
221
257
|
/**
|
|
222
258
|
* @since 1.0.0
|
|
223
259
|
* @category handlers
|
|
@@ -229,14 +265,90 @@ export declare namespace Handlers {
|
|
|
229
265
|
* @category handlers
|
|
230
266
|
*/
|
|
231
267
|
export type Item<E, R> = {
|
|
232
|
-
readonly _tag: "Handler"
|
|
233
268
|
readonly endpoint: HttpApiEndpoint.HttpApiEndpoint.Any
|
|
234
269
|
readonly handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, E, R>
|
|
235
270
|
readonly withFullResponse: boolean
|
|
236
|
-
} | {
|
|
237
|
-
readonly _tag: "Middleware"
|
|
238
|
-
readonly middleware: Middleware<any, any, E, R>
|
|
239
271
|
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @since 1.0.0
|
|
275
|
+
* @category handlers
|
|
276
|
+
*/
|
|
277
|
+
export type FromGroup<
|
|
278
|
+
ApiError,
|
|
279
|
+
ApiR,
|
|
280
|
+
Group extends HttpApiGroup.HttpApiGroup.Any
|
|
281
|
+
> = Handlers<
|
|
282
|
+
| ApiError
|
|
283
|
+
| HttpApiGroup.HttpApiGroup.Error<Group>,
|
|
284
|
+
| HttpApiMiddleware.HttpApiMiddleware.ExtractProvides<ApiR>
|
|
285
|
+
| HttpApiGroup.HttpApiGroup.Provides<Group>,
|
|
286
|
+
never,
|
|
287
|
+
HttpApiGroup.HttpApiGroup.Endpoints<Group>
|
|
288
|
+
>
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @since 1.0.0
|
|
292
|
+
* @category handlers
|
|
293
|
+
*/
|
|
294
|
+
export type ValidateReturn<A> = A extends (
|
|
295
|
+
| Handlers<
|
|
296
|
+
infer _E,
|
|
297
|
+
infer _Provides,
|
|
298
|
+
infer _R,
|
|
299
|
+
infer _Endpoints
|
|
300
|
+
>
|
|
301
|
+
| Effect.Effect<
|
|
302
|
+
Handlers<
|
|
303
|
+
infer _E,
|
|
304
|
+
infer _Provides,
|
|
305
|
+
infer _R,
|
|
306
|
+
infer _Endpoints
|
|
307
|
+
>,
|
|
308
|
+
infer _EX,
|
|
309
|
+
infer _RX
|
|
310
|
+
>
|
|
311
|
+
) ? [_Endpoints] extends [never] ? A
|
|
312
|
+
: `Endpoint not handled: ${HttpApiEndpoint.HttpApiEndpoint.Name<_Endpoints>}` :
|
|
313
|
+
`Must return the implemented handlers`
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @since 1.0.0
|
|
317
|
+
* @category handlers
|
|
318
|
+
*/
|
|
319
|
+
export type Error<A> = A extends Effect.Effect<
|
|
320
|
+
Handlers<
|
|
321
|
+
infer _E,
|
|
322
|
+
infer _Provides,
|
|
323
|
+
infer _R,
|
|
324
|
+
infer _Endpoints
|
|
325
|
+
>,
|
|
326
|
+
infer _EX,
|
|
327
|
+
infer _RX
|
|
328
|
+
> ? _EX :
|
|
329
|
+
never
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* @since 1.0.0
|
|
333
|
+
* @category handlers
|
|
334
|
+
*/
|
|
335
|
+
export type Context<A> = A extends Handlers<
|
|
336
|
+
infer _E,
|
|
337
|
+
infer _Provides,
|
|
338
|
+
infer _R,
|
|
339
|
+
infer _Endpoints
|
|
340
|
+
> ? _R :
|
|
341
|
+
A extends Effect.Effect<
|
|
342
|
+
Handlers<
|
|
343
|
+
infer _E,
|
|
344
|
+
infer _Provides,
|
|
345
|
+
infer _R,
|
|
346
|
+
infer _Endpoints
|
|
347
|
+
>,
|
|
348
|
+
infer _EX,
|
|
349
|
+
infer _RX
|
|
350
|
+
> ? _R | _RX :
|
|
351
|
+
never
|
|
240
352
|
}
|
|
241
353
|
|
|
242
354
|
const HandlersProto = {
|
|
@@ -245,15 +357,45 @@ const HandlersProto = {
|
|
|
245
357
|
},
|
|
246
358
|
pipe() {
|
|
247
359
|
return pipeArguments(this, arguments)
|
|
360
|
+
},
|
|
361
|
+
handle(
|
|
362
|
+
this: Handlers<any, any, any, HttpApiEndpoint.HttpApiEndpoint.Any>,
|
|
363
|
+
name: string,
|
|
364
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, any, any>
|
|
365
|
+
) {
|
|
366
|
+
const endpoint = HashMap.unsafeGet(this.group.endpoints, name)
|
|
367
|
+
return makeHandlers({
|
|
368
|
+
group: this.group,
|
|
369
|
+
handlers: Chunk.append(this.handlers, {
|
|
370
|
+
endpoint,
|
|
371
|
+
handler,
|
|
372
|
+
withFullResponse: false
|
|
373
|
+
}) as any
|
|
374
|
+
})
|
|
375
|
+
},
|
|
376
|
+
handleRaw(
|
|
377
|
+
this: Handlers<any, any, any, HttpApiEndpoint.HttpApiEndpoint.Any>,
|
|
378
|
+
name: string,
|
|
379
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, any, any>
|
|
380
|
+
) {
|
|
381
|
+
const endpoint = HashMap.unsafeGet(this.group.endpoints, name)
|
|
382
|
+
return makeHandlers({
|
|
383
|
+
group: this.group,
|
|
384
|
+
handlers: Chunk.append(this.handlers, {
|
|
385
|
+
endpoint,
|
|
386
|
+
handler,
|
|
387
|
+
withFullResponse: true
|
|
388
|
+
}) as any
|
|
389
|
+
})
|
|
248
390
|
}
|
|
249
391
|
}
|
|
250
392
|
|
|
251
|
-
const makeHandlers = <E, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.
|
|
393
|
+
const makeHandlers = <E, Provides, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Any>(
|
|
252
394
|
options: {
|
|
253
|
-
readonly group: HttpApiGroup.HttpApiGroup
|
|
395
|
+
readonly group: HttpApiGroup.HttpApiGroup.Any
|
|
254
396
|
readonly handlers: Chunk.Chunk<Handlers.Item<E, R>>
|
|
255
397
|
}
|
|
256
|
-
): Handlers<E, R, Endpoints> => {
|
|
398
|
+
): Handlers<E, Provides, R, Endpoints> => {
|
|
257
399
|
const self = Object.create(HandlersProto)
|
|
258
400
|
self.group = options.group
|
|
259
401
|
self.handlers = options.handlers
|
|
@@ -261,12 +403,12 @@ const makeHandlers = <E, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Al
|
|
|
261
403
|
}
|
|
262
404
|
|
|
263
405
|
/**
|
|
264
|
-
* Create a `Layer` that will implement all the endpoints in an `
|
|
406
|
+
* Create a `Layer` that will implement all the endpoints in an `HttpApi`.
|
|
265
407
|
*
|
|
266
408
|
* An unimplemented `Handlers` instance is passed to the `build` function, which
|
|
267
409
|
* you can use to add handlers to the group.
|
|
268
410
|
*
|
|
269
|
-
* You can implement endpoints using the `
|
|
411
|
+
* You can implement endpoints using the `handlers.handle` api.
|
|
270
412
|
*
|
|
271
413
|
* @since 1.0.0
|
|
272
414
|
* @category handlers
|
|
@@ -274,146 +416,303 @@ const makeHandlers = <E, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Al
|
|
|
274
416
|
export const group = <
|
|
275
417
|
Groups extends HttpApiGroup.HttpApiGroup.Any,
|
|
276
418
|
ApiError,
|
|
277
|
-
|
|
278
|
-
const Name extends Groups
|
|
279
|
-
|
|
280
|
-
EX = never,
|
|
281
|
-
RX = never
|
|
419
|
+
ApiR,
|
|
420
|
+
const Name extends HttpApiGroup.HttpApiGroup.Name<Groups>,
|
|
421
|
+
Return
|
|
282
422
|
>(
|
|
283
|
-
api: HttpApi.HttpApi<Groups, ApiError,
|
|
423
|
+
api: HttpApi.HttpApi<Groups, ApiError, ApiR>,
|
|
284
424
|
groupName: Name,
|
|
285
425
|
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>
|
|
426
|
+
handlers: Handlers.FromGroup<ApiError, ApiR, HttpApiGroup.HttpApiGroup.WithName<Groups, Name>>
|
|
427
|
+
) => Handlers.ValidateReturn<Return>
|
|
290
428
|
): Layer.Layer<
|
|
291
|
-
HttpApiGroup.
|
|
292
|
-
|
|
293
|
-
|
|
429
|
+
HttpApiGroup.Group<Name>,
|
|
430
|
+
Handlers.Error<Return>,
|
|
431
|
+
| Handlers.Context<Return>
|
|
432
|
+
| HttpApiGroup.HttpApiGroup.ContextWithName<Groups, Name>
|
|
294
433
|
> =>
|
|
295
434
|
Router.use((router) =>
|
|
296
435
|
Effect.gen(function*() {
|
|
297
436
|
const context = yield* Effect.context<any>()
|
|
298
|
-
const group =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
437
|
+
const group = HashMap.unsafeGet(api.groups, groupName)
|
|
438
|
+
const result = build(makeHandlers({ group, handlers: Chunk.empty() }))
|
|
439
|
+
const handlers: Handlers<any, any, any> = Effect.isEffect(result)
|
|
440
|
+
? (yield* result as Effect.Effect<any, any, any>)
|
|
441
|
+
: result
|
|
442
|
+
const groupMiddleware = makeMiddlewareMap((group as any).middlewares, context)
|
|
304
443
|
const routes: Array<HttpRouter.Route<any, any>> = []
|
|
305
444
|
for (const item of handlers.handlers) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
},
|
|
319
|
-
item.withFullResponse
|
|
320
|
-
))
|
|
321
|
-
}
|
|
445
|
+
const middleware = makeMiddlewareMap((item as any).endpoint.middlewares, context, groupMiddleware)
|
|
446
|
+
routes.push(handlerToRoute(
|
|
447
|
+
item.endpoint,
|
|
448
|
+
middleware,
|
|
449
|
+
function(request) {
|
|
450
|
+
return Effect.mapInputContext(
|
|
451
|
+
item.handler(request),
|
|
452
|
+
(input) => Context.merge(context, input)
|
|
453
|
+
)
|
|
454
|
+
},
|
|
455
|
+
item.withFullResponse
|
|
456
|
+
))
|
|
322
457
|
}
|
|
323
458
|
yield* router.concat(HttpRouter.fromIterable(routes))
|
|
324
459
|
})
|
|
325
460
|
) as any
|
|
326
461
|
|
|
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
|
-
|
|
462
|
+
// internal
|
|
463
|
+
|
|
464
|
+
const requestPayload = (
|
|
465
|
+
request: HttpServerRequest.HttpServerRequest,
|
|
466
|
+
urlParams: ReadonlyRecord<string, string | Array<string>>,
|
|
467
|
+
isMultipart: boolean
|
|
468
|
+
): Effect.Effect<
|
|
469
|
+
unknown,
|
|
470
|
+
never,
|
|
471
|
+
| FileSystem
|
|
472
|
+
| Path
|
|
473
|
+
| Scope
|
|
474
|
+
> =>
|
|
475
|
+
HttpMethod.hasBody(request.method)
|
|
476
|
+
? isMultipart
|
|
477
|
+
? Effect.orDie(request.multipart)
|
|
478
|
+
: Effect.orDie(request.json)
|
|
479
|
+
: Effect.succeed(urlParams)
|
|
480
|
+
|
|
481
|
+
type MiddlewareMap = Map<string, {
|
|
482
|
+
readonly tag: HttpApiMiddleware.TagClassAny
|
|
483
|
+
readonly effect: Effect.Effect<any, any, any>
|
|
484
|
+
}>
|
|
485
|
+
|
|
486
|
+
const makeMiddlewareMap = (
|
|
487
|
+
middleware: HashSet.HashSet<HttpApiMiddleware.TagClassAny>,
|
|
488
|
+
context: Context.Context<never>,
|
|
489
|
+
initial?: MiddlewareMap
|
|
490
|
+
): MiddlewareMap => {
|
|
491
|
+
const map = new Map<string, {
|
|
492
|
+
readonly tag: HttpApiMiddleware.TagClassAny
|
|
493
|
+
readonly effect: Effect.Effect<any, any, any>
|
|
494
|
+
}>(initial)
|
|
495
|
+
HashSet.forEach(middleware, (tag) => {
|
|
496
|
+
map.set(tag.key, {
|
|
497
|
+
tag,
|
|
498
|
+
effect: Context.unsafeGet(context, tag as any)
|
|
499
|
+
})
|
|
500
|
+
})
|
|
501
|
+
return map
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const handlerToRoute = (
|
|
505
|
+
endpoint_: HttpApiEndpoint.HttpApiEndpoint.Any,
|
|
506
|
+
middleware: MiddlewareMap,
|
|
507
|
+
handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, any, any>,
|
|
508
|
+
isFullResponse: boolean
|
|
509
|
+
): HttpRouter.Route<any, any> => {
|
|
510
|
+
const endpoint = endpoint_ as HttpApiEndpoint.HttpApiEndpoint.AnyWithProps
|
|
511
|
+
const decodePath = Option.map(endpoint.pathSchema, Schema.decodeUnknown)
|
|
512
|
+
const isMultipart = endpoint.payloadSchema.pipe(
|
|
513
|
+
Option.map((schema) => HttpApiSchema.getMultipart(schema.ast)),
|
|
514
|
+
Option.getOrElse(() => false)
|
|
515
|
+
)
|
|
516
|
+
const decodePayload = Option.map(endpoint.payloadSchema, Schema.decodeUnknown)
|
|
517
|
+
const decodeHeaders = Option.map(endpoint.headersSchema, Schema.decodeUnknown)
|
|
518
|
+
const decodeUrlParams = Option.map(endpoint.urlParamsSchema, Schema.decodeUnknown)
|
|
519
|
+
const encodeSuccess = Schema.encode(makeSuccessSchema(endpoint.successSchema))
|
|
520
|
+
return HttpRouter.makeRoute(
|
|
521
|
+
endpoint.method,
|
|
522
|
+
endpoint.path,
|
|
523
|
+
applyMiddleware(
|
|
524
|
+
middleware,
|
|
525
|
+
Effect.withFiberRuntime((fiber) => {
|
|
526
|
+
const context = fiber.getFiberRef(FiberRef.currentContext)
|
|
527
|
+
const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest)
|
|
528
|
+
const routeContext = Context.unsafeGet(context, HttpRouter.RouteContext)
|
|
529
|
+
const urlParams = Context.unsafeGet(context, HttpServerRequest.ParsedSearchParams)
|
|
530
|
+
return (
|
|
531
|
+
decodePath._tag === "Some"
|
|
532
|
+
? decodePath.value(routeContext.params)
|
|
533
|
+
: Effect.succeed(routeContext.params)
|
|
534
|
+
).pipe(
|
|
535
|
+
Effect.bindTo("pathParams"),
|
|
536
|
+
decodePayload._tag === "Some"
|
|
537
|
+
? Effect.bind(
|
|
538
|
+
"payload",
|
|
539
|
+
(_) =>
|
|
540
|
+
Effect.flatMap(
|
|
541
|
+
requestPayload(request, urlParams, isMultipart),
|
|
542
|
+
decodePayload.value
|
|
543
|
+
)
|
|
544
|
+
) as typeof identity
|
|
545
|
+
: identity,
|
|
546
|
+
decodeHeaders._tag === "Some"
|
|
547
|
+
? Effect.bind("headers", (_) => decodeHeaders.value(request.headers)) as typeof identity
|
|
548
|
+
: identity,
|
|
549
|
+
decodeUrlParams._tag === "Some"
|
|
550
|
+
? Effect.bind("urlParams", (_) => decodeUrlParams.value(urlParams)) as typeof identity
|
|
551
|
+
: identity,
|
|
552
|
+
Effect.flatMap((input) => {
|
|
553
|
+
const request: any = { path: input.pathParams }
|
|
554
|
+
if ("payload" in input) {
|
|
555
|
+
request.payload = input.payload
|
|
556
|
+
}
|
|
557
|
+
if ("headers" in input) {
|
|
558
|
+
request.headers = input.headers
|
|
559
|
+
}
|
|
560
|
+
if ("urlParams" in input) {
|
|
561
|
+
request.urlParams = input.urlParams
|
|
562
|
+
}
|
|
563
|
+
return handler(request)
|
|
564
|
+
}),
|
|
565
|
+
isFullResponse ?
|
|
566
|
+
identity as (_: any) => Effect.Effect<HttpServerResponse.HttpServerResponse> :
|
|
567
|
+
Effect.flatMap(encodeSuccess),
|
|
568
|
+
Effect.catchIf(ParseResult.isParseError, HttpApiDecodeError.refailParseError)
|
|
569
|
+
)
|
|
570
|
+
})
|
|
571
|
+
)
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const applyMiddleware = <A extends Effect.Effect<any, any, any>>(
|
|
576
|
+
middleware: MiddlewareMap,
|
|
577
|
+
handler: A
|
|
578
|
+
) => {
|
|
579
|
+
for (const entry of middleware.values()) {
|
|
580
|
+
const effect = HttpApiMiddleware.SecurityTypeId in entry.tag ? makeSecurityMiddleware(entry as any) : entry.effect
|
|
581
|
+
if (entry.tag.optional) {
|
|
582
|
+
const previous = handler
|
|
583
|
+
handler = Effect.matchEffect(effect, {
|
|
584
|
+
onFailure: () => previous,
|
|
585
|
+
onSuccess: entry.tag.provides !== undefined
|
|
586
|
+
? (value) => Effect.provideService(previous, entry.tag.provides as any, value)
|
|
587
|
+
: (_) => previous
|
|
588
|
+
}) as any
|
|
589
|
+
} else {
|
|
590
|
+
handler = entry.tag.provides !== undefined
|
|
591
|
+
? Effect.provideServiceEffect(handler, entry.tag.provides as any, effect) as any
|
|
592
|
+
: Effect.zipRight(effect, handler) as any
|
|
359
593
|
}
|
|
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
594
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
595
|
+
return handler
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const securityMiddlewareCache = globalValue<WeakMap<any, Effect.Effect<any, any, any>>>(
|
|
599
|
+
"securityMiddlewareCache",
|
|
600
|
+
() => new WeakMap()
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
const makeSecurityMiddleware = (
|
|
604
|
+
entry: {
|
|
605
|
+
readonly tag: HttpApiMiddleware.TagClassSecurityAny
|
|
606
|
+
readonly effect: Record<string, (_: any) => Effect.Effect<any, any>>
|
|
607
|
+
}
|
|
608
|
+
): Effect.Effect<any, any, any> => {
|
|
609
|
+
if (securityMiddlewareCache.has(entry.tag)) {
|
|
610
|
+
return securityMiddlewareCache.get(entry.tag)!
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
let effect: Effect.Effect<any, any, any> | undefined
|
|
614
|
+
for (const [key, security] of Object.entries(entry.tag.security)) {
|
|
615
|
+
const decode = securityDecode(security)
|
|
616
|
+
const handler = entry.effect[key]
|
|
617
|
+
const middleware = Effect.flatMap(decode, handler)
|
|
618
|
+
effect = effect === undefined ? middleware : Effect.catchAll(effect, () => middleware)
|
|
619
|
+
}
|
|
620
|
+
if (effect === undefined) {
|
|
621
|
+
effect = Effect.void
|
|
382
622
|
}
|
|
383
|
-
|
|
384
|
-
return
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
623
|
+
securityMiddlewareCache.set(entry.tag, effect)
|
|
624
|
+
return effect
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const responseSchema = Schema.declare(HttpServerResponse.isServerResponse)
|
|
628
|
+
|
|
629
|
+
const makeSuccessSchema = (
|
|
630
|
+
schema: Schema.Schema.Any
|
|
631
|
+
): Schema.Schema<unknown, HttpServerResponse.HttpServerResponse> => {
|
|
632
|
+
const schemas = new Set<Schema.Schema.Any>()
|
|
633
|
+
HttpApiSchema.deunionize(schemas, schema)
|
|
634
|
+
return Schema.Union(...Array.from(schemas, toResponseSuccess)) as any
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const makeErrorSchema = (
|
|
638
|
+
api: HttpApi.HttpApi.AnyWithProps
|
|
639
|
+
): Schema.Schema<unknown, HttpServerResponse.HttpServerResponse> => {
|
|
640
|
+
const schemas = new Set<Schema.Schema.Any>()
|
|
641
|
+
HttpApiSchema.deunionize(schemas, api.errorSchema)
|
|
642
|
+
HashMap.forEach(api.groups, (group) => {
|
|
643
|
+
HashMap.forEach(group.endpoints, (endpoint) => {
|
|
644
|
+
HttpApiSchema.deunionize(schemas, endpoint.errorSchema)
|
|
645
|
+
})
|
|
646
|
+
HttpApiSchema.deunionize(schemas, group.errorSchema)
|
|
392
647
|
})
|
|
648
|
+
return Schema.Union(...Array.from(schemas, toResponseError)) as any
|
|
393
649
|
}
|
|
394
650
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
651
|
+
const decodeForbidden = <A>(_: A, __: AST.ParseOptions, ast: AST.Transformation) =>
|
|
652
|
+
ParseResult.fail(new ParseResult.Forbidden(ast, _, "Encode only schema"))
|
|
653
|
+
|
|
654
|
+
const toResponseSchema = (getStatus: (ast: AST.AST) => number) => {
|
|
655
|
+
const cache = new WeakMap<AST.AST, Schema.Schema.All>()
|
|
656
|
+
const schemaToResponse = (
|
|
657
|
+
data: any,
|
|
658
|
+
_: AST.ParseOptions,
|
|
659
|
+
ast: AST.Transformation
|
|
660
|
+
): Effect.Effect<HttpServerResponse.HttpServerResponse, ParseResult.ParseIssue> => {
|
|
661
|
+
const isEmpty = HttpApiSchema.isVoid(ast.to)
|
|
662
|
+
const status = getStatus(ast.to)
|
|
663
|
+
if (isEmpty) {
|
|
664
|
+
return HttpServerResponse.empty({ status })
|
|
665
|
+
}
|
|
666
|
+
const encoding = HttpApiSchema.getEncoding(ast.to)
|
|
667
|
+
switch (encoding.kind) {
|
|
668
|
+
case "Json": {
|
|
669
|
+
return Effect.mapError(
|
|
670
|
+
HttpServerResponse.json(data, {
|
|
671
|
+
status,
|
|
672
|
+
contentType: encoding.contentType
|
|
673
|
+
}),
|
|
674
|
+
(error) => new ParseResult.Type(ast, error, "Could not encode to JSON")
|
|
675
|
+
)
|
|
676
|
+
}
|
|
677
|
+
case "Text": {
|
|
678
|
+
return ParseResult.succeed(HttpServerResponse.text(data as any, {
|
|
679
|
+
status,
|
|
680
|
+
contentType: encoding.contentType
|
|
681
|
+
}))
|
|
682
|
+
}
|
|
683
|
+
case "Uint8Array": {
|
|
684
|
+
return ParseResult.succeed(HttpServerResponse.uint8Array(data as any, {
|
|
685
|
+
status,
|
|
686
|
+
contentType: encoding.contentType
|
|
687
|
+
}))
|
|
688
|
+
}
|
|
689
|
+
case "UrlParams": {
|
|
690
|
+
return ParseResult.succeed(HttpServerResponse.urlParams(data as any, {
|
|
691
|
+
status,
|
|
692
|
+
contentType: encoding.contentType
|
|
693
|
+
}))
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return <A, I, R>(schema: Schema.Schema<A, I, R>): Schema.Schema<A, HttpServerResponse.HttpServerResponse, R> => {
|
|
698
|
+
if (cache.has(schema.ast)) {
|
|
699
|
+
return cache.get(schema.ast)! as any
|
|
700
|
+
}
|
|
701
|
+
const transform = Schema.transformOrFail(responseSchema, schema, {
|
|
702
|
+
decode: decodeForbidden,
|
|
703
|
+
encode: schemaToResponse
|
|
416
704
|
})
|
|
705
|
+
cache.set(transform.ast, transform)
|
|
706
|
+
return transform
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const toResponseSuccess = toResponseSchema(HttpApiSchema.getStatusSuccessAST)
|
|
711
|
+
const toResponseError = toResponseSchema(HttpApiSchema.getStatusErrorAST)
|
|
712
|
+
|
|
713
|
+
// ----------------------------------------------------------------------------
|
|
714
|
+
// Global middleware
|
|
715
|
+
// ----------------------------------------------------------------------------
|
|
417
716
|
|
|
418
717
|
/**
|
|
419
718
|
* @since 1.0.0
|
|
@@ -426,17 +725,11 @@ export class Middleware extends Context.Tag("@effect/platform/HttpApiBuilder/Mid
|
|
|
426
725
|
|
|
427
726
|
/**
|
|
428
727
|
* @since 1.0.0
|
|
429
|
-
* @category
|
|
728
|
+
* @category global
|
|
430
729
|
*/
|
|
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
|
-
}
|
|
730
|
+
export type MiddlewareFn<Error, R = HttpRouter.HttpRouter.Provided> = (
|
|
731
|
+
httpApp: HttpApp.Default
|
|
732
|
+
) => HttpApp.Default<Error, R>
|
|
440
733
|
|
|
441
734
|
const middlewareAdd = (middleware: HttpMiddleware.HttpMiddleware): Effect.Effect<HttpMiddleware.HttpMiddleware> =>
|
|
442
735
|
Effect.map(
|
|
@@ -465,7 +758,7 @@ const middlewareAddNoContext = (
|
|
|
465
758
|
* @since 1.0.0
|
|
466
759
|
* @category middleware
|
|
467
760
|
*/
|
|
468
|
-
export const
|
|
761
|
+
export const middleware: {
|
|
469
762
|
/**
|
|
470
763
|
* Create an `HttpApi` level middleware `Layer`.
|
|
471
764
|
*
|
|
@@ -473,7 +766,7 @@ export const middlewareLayer: {
|
|
|
473
766
|
* @category middleware
|
|
474
767
|
*/
|
|
475
768
|
<EX = never, RX = never>(
|
|
476
|
-
middleware:
|
|
769
|
+
middleware: MiddlewareFn<never> | Effect.Effect<MiddlewareFn<never>, EX, RX>,
|
|
477
770
|
options?: {
|
|
478
771
|
readonly withContext?: false | undefined
|
|
479
772
|
}
|
|
@@ -485,7 +778,7 @@ export const middlewareLayer: {
|
|
|
485
778
|
* @category middleware
|
|
486
779
|
*/
|
|
487
780
|
<R, EX = never, RX = never>(
|
|
488
|
-
middleware:
|
|
781
|
+
middleware: MiddlewareFn<never, R> | Effect.Effect<MiddlewareFn<never, R>, EX, RX>,
|
|
489
782
|
options: {
|
|
490
783
|
readonly withContext: true
|
|
491
784
|
}
|
|
@@ -498,7 +791,7 @@ export const middlewareLayer: {
|
|
|
498
791
|
*/
|
|
499
792
|
<Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR, EX = never, RX = never>(
|
|
500
793
|
api: HttpApi.HttpApi<Groups, Error, ErrorR>,
|
|
501
|
-
middleware:
|
|
794
|
+
middleware: MiddlewareFn<NoInfer<Error>> | Effect.Effect<MiddlewareFn<NoInfer<Error>>, EX, RX>,
|
|
502
795
|
options?: {
|
|
503
796
|
readonly withContext?: false | undefined
|
|
504
797
|
}
|
|
@@ -511,20 +804,20 @@ export const middlewareLayer: {
|
|
|
511
804
|
*/
|
|
512
805
|
<Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR, R, EX = never, RX = never>(
|
|
513
806
|
api: HttpApi.HttpApi<Groups, Error, ErrorR>,
|
|
514
|
-
middleware:
|
|
807
|
+
middleware: MiddlewareFn<NoInfer<Error>, R> | Effect.Effect<MiddlewareFn<NoInfer<Error>, R>, EX, RX>,
|
|
515
808
|
options: {
|
|
516
809
|
readonly withContext: true
|
|
517
810
|
}
|
|
518
811
|
): Layer.Layer<never, EX, HttpRouter.HttpRouter.ExcludeProvided<R> | RX>
|
|
519
812
|
} = (
|
|
520
813
|
...args: [
|
|
521
|
-
middleware:
|
|
814
|
+
middleware: MiddlewareFn<any, any> | Effect.Effect<MiddlewareFn<any, any>, any, any>,
|
|
522
815
|
options?: {
|
|
523
816
|
readonly withContext?: boolean | undefined
|
|
524
817
|
} | undefined
|
|
525
818
|
] | [
|
|
526
819
|
api: HttpApi.HttpApi.Any,
|
|
527
|
-
middleware:
|
|
820
|
+
middleware: MiddlewareFn<any, any> | Effect.Effect<MiddlewareFn<any, any>, any, any>,
|
|
528
821
|
options?: {
|
|
529
822
|
readonly withContext?: boolean | undefined
|
|
530
823
|
} | undefined
|
|
@@ -546,7 +839,7 @@ export const middlewareLayer: {
|
|
|
546
839
|
* @since 1.0.0
|
|
547
840
|
* @category middleware
|
|
548
841
|
*/
|
|
549
|
-
export const
|
|
842
|
+
export const middlewareScoped: {
|
|
550
843
|
/**
|
|
551
844
|
* Create an `HttpApi` level middleware `Layer`, that has a `Scope` provided to
|
|
552
845
|
* the constructor.
|
|
@@ -555,7 +848,7 @@ export const middlewareLayerScoped: {
|
|
|
555
848
|
* @category middleware
|
|
556
849
|
*/
|
|
557
850
|
<EX, RX>(
|
|
558
|
-
middleware: Effect.Effect<
|
|
851
|
+
middleware: Effect.Effect<MiddlewareFn<never>, EX, RX>,
|
|
559
852
|
options?: {
|
|
560
853
|
readonly withContext?: false | undefined
|
|
561
854
|
}
|
|
@@ -568,7 +861,7 @@ export const middlewareLayerScoped: {
|
|
|
568
861
|
* @category middleware
|
|
569
862
|
*/
|
|
570
863
|
<R, EX, RX>(
|
|
571
|
-
middleware: Effect.Effect<
|
|
864
|
+
middleware: Effect.Effect<MiddlewareFn<never, R>, EX, RX>,
|
|
572
865
|
options: {
|
|
573
866
|
readonly withContext: true
|
|
574
867
|
}
|
|
@@ -582,7 +875,7 @@ export const middlewareLayerScoped: {
|
|
|
582
875
|
*/
|
|
583
876
|
<Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR, EX, RX>(
|
|
584
877
|
api: HttpApi.HttpApi<Groups, Error, ErrorR>,
|
|
585
|
-
middleware: Effect.Effect<
|
|
878
|
+
middleware: Effect.Effect<MiddlewareFn<NoInfer<Error>>, EX, RX>,
|
|
586
879
|
options?: {
|
|
587
880
|
readonly withContext?: false | undefined
|
|
588
881
|
}
|
|
@@ -596,20 +889,20 @@ export const middlewareLayerScoped: {
|
|
|
596
889
|
*/
|
|
597
890
|
<Groups extends HttpApiGroup.HttpApiGroup.Any, Error, ErrorR, R, EX, RX>(
|
|
598
891
|
api: HttpApi.HttpApi<Groups, Error, ErrorR>,
|
|
599
|
-
middleware: Effect.Effect<
|
|
892
|
+
middleware: Effect.Effect<MiddlewareFn<NoInfer<Error>, R>, EX, RX>,
|
|
600
893
|
options: {
|
|
601
894
|
readonly withContext: true
|
|
602
895
|
}
|
|
603
896
|
): Layer.Layer<never, EX, HttpRouter.HttpRouter.ExcludeProvided<R> | Exclude<RX, Scope>>
|
|
604
897
|
} = (
|
|
605
898
|
...args: [
|
|
606
|
-
middleware:
|
|
899
|
+
middleware: MiddlewareFn<any, any> | Effect.Effect<MiddlewareFn<any, any>, any, any>,
|
|
607
900
|
options?: {
|
|
608
901
|
readonly withContext?: boolean | undefined
|
|
609
902
|
} | undefined
|
|
610
903
|
] | [
|
|
611
904
|
api: HttpApi.HttpApi.Any,
|
|
612
|
-
middleware:
|
|
905
|
+
middleware: MiddlewareFn<any, any> | Effect.Effect<MiddlewareFn<any, any>, any, any>,
|
|
613
906
|
options?: {
|
|
614
907
|
readonly withContext?: boolean | undefined
|
|
615
908
|
} | undefined
|
|
@@ -637,7 +930,7 @@ export const middlewareCors = (
|
|
|
637
930
|
readonly maxAge?: number | undefined
|
|
638
931
|
readonly credentials?: boolean | undefined
|
|
639
932
|
} | undefined
|
|
640
|
-
): Layer.Layer<never> =>
|
|
933
|
+
): Layer.Layer<never> => middleware(HttpMiddleware.cors(options))
|
|
641
934
|
|
|
642
935
|
/**
|
|
643
936
|
* A middleware that adds an openapi.json endpoint to the API.
|
|
@@ -649,10 +942,10 @@ export const middlewareOpenApi = (
|
|
|
649
942
|
options?: {
|
|
650
943
|
readonly path?: HttpRouter.PathInput | undefined
|
|
651
944
|
} | undefined
|
|
652
|
-
): Layer.Layer<never, never, HttpApi.
|
|
945
|
+
): Layer.Layer<never, never, HttpApi.Api> =>
|
|
653
946
|
Router.use((router) =>
|
|
654
947
|
Effect.gen(function*() {
|
|
655
|
-
const api = yield* HttpApi.
|
|
948
|
+
const { api } = yield* HttpApi.Api
|
|
656
949
|
const spec = OpenApi.fromApi(api)
|
|
657
950
|
const response = yield* HttpServerResponse.json(spec).pipe(
|
|
658
951
|
Effect.orDie
|
|
@@ -661,21 +954,11 @@ export const middlewareOpenApi = (
|
|
|
661
954
|
})
|
|
662
955
|
)
|
|
663
956
|
|
|
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
957
|
const bearerLen = `Bearer `.length
|
|
675
958
|
|
|
676
959
|
/**
|
|
677
960
|
* @since 1.0.0
|
|
678
|
-
* @category
|
|
961
|
+
* @category security
|
|
679
962
|
*/
|
|
680
963
|
export const securityDecode = <Security extends HttpApiSecurity.HttpApiSecurity>(
|
|
681
964
|
self: Security
|
|
@@ -738,9 +1021,9 @@ export const securityDecode = <Security extends HttpApiSecurity.HttpApiSecurity>
|
|
|
738
1021
|
* You can use this api before returning a response from an endpoint handler.
|
|
739
1022
|
*
|
|
740
1023
|
* ```ts
|
|
741
|
-
*
|
|
1024
|
+
* handlers.handle(
|
|
742
1025
|
* "authenticate",
|
|
743
|
-
* (_) =>
|
|
1026
|
+
* (_) => HttpApiBuilder.securitySetCookie(security, "secret123")
|
|
744
1027
|
* )
|
|
745
1028
|
* ```
|
|
746
1029
|
*
|
|
@@ -763,233 +1046,3 @@ export const securitySetCookie = (
|
|
|
763
1046
|
)
|
|
764
1047
|
)
|
|
765
1048
|
}
|
|
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
|
-
}
|