@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.
Files changed (229) hide show
  1. package/HttpApiMiddleware/package.json +6 -0
  2. package/README.md +306 -233
  3. package/dist/cjs/Headers.js +7 -2
  4. package/dist/cjs/Headers.js.map +1 -1
  5. package/dist/cjs/HttpApi.js +90 -78
  6. package/dist/cjs/HttpApi.js.map +1 -1
  7. package/dist/cjs/HttpApiBuilder.js +243 -255
  8. package/dist/cjs/HttpApiBuilder.js.map +1 -1
  9. package/dist/cjs/HttpApiClient.js +64 -59
  10. package/dist/cjs/HttpApiClient.js.map +1 -1
  11. package/dist/cjs/HttpApiEndpoint.js +74 -109
  12. package/dist/cjs/HttpApiEndpoint.js.map +1 -1
  13. package/dist/cjs/HttpApiError.js +3 -4
  14. package/dist/cjs/HttpApiError.js.map +1 -1
  15. package/dist/cjs/HttpApiGroup.js +103 -100
  16. package/dist/cjs/HttpApiGroup.js.map +1 -1
  17. package/dist/cjs/HttpApiMiddleware.js +67 -0
  18. package/dist/cjs/HttpApiMiddleware.js.map +1 -0
  19. package/dist/cjs/HttpApiSchema.js +33 -7
  20. package/dist/cjs/HttpApiSchema.js.map +1 -1
  21. package/dist/cjs/HttpApiSecurity.js +2 -2
  22. package/dist/cjs/HttpApiSecurity.js.map +1 -1
  23. package/dist/cjs/HttpApiSwagger.js +3 -1
  24. package/dist/cjs/HttpApiSwagger.js.map +1 -1
  25. package/dist/cjs/HttpBody.js.map +1 -1
  26. package/dist/cjs/HttpIncomingMessage.js +5 -1
  27. package/dist/cjs/HttpIncomingMessage.js.map +1 -1
  28. package/dist/cjs/HttpServer.js +12 -1
  29. package/dist/cjs/HttpServer.js.map +1 -1
  30. package/dist/cjs/HttpServerRespondable.js +1 -1
  31. package/dist/cjs/HttpServerRespondable.js.map +1 -1
  32. package/dist/cjs/OpenApi.js +102 -63
  33. package/dist/cjs/OpenApi.js.map +1 -1
  34. package/dist/cjs/OpenApiJsonSchema.js +58 -47
  35. package/dist/cjs/OpenApiJsonSchema.js.map +1 -1
  36. package/dist/cjs/Transferable.js +2 -2
  37. package/dist/cjs/Transferable.js.map +1 -1
  38. package/dist/cjs/UrlParams.js +5 -1
  39. package/dist/cjs/UrlParams.js.map +1 -1
  40. package/dist/cjs/Worker.js.map +1 -1
  41. package/dist/cjs/WorkerError.js +1 -5
  42. package/dist/cjs/WorkerError.js.map +1 -1
  43. package/dist/cjs/WorkerRunner.js.map +1 -1
  44. package/dist/cjs/index.js +3 -1
  45. package/dist/cjs/internal/httpBody.js +1 -1
  46. package/dist/cjs/internal/httpBody.js.map +1 -1
  47. package/dist/cjs/internal/httpClientRequest.js.map +1 -1
  48. package/dist/cjs/internal/httpClientResponse.js +1 -1
  49. package/dist/cjs/internal/httpClientResponse.js.map +1 -1
  50. package/dist/cjs/internal/httpRouter.js +1 -1
  51. package/dist/cjs/internal/httpRouter.js.map +1 -1
  52. package/dist/cjs/internal/httpServer.js +7 -1
  53. package/dist/cjs/internal/httpServer.js.map +1 -1
  54. package/dist/cjs/internal/httpServerRequest.js +1 -1
  55. package/dist/cjs/internal/httpServerRequest.js.map +1 -1
  56. package/dist/cjs/internal/httpServerResponse.js.map +1 -1
  57. package/dist/cjs/internal/keyValueStore.js +1 -1
  58. package/dist/cjs/internal/keyValueStore.js.map +1 -1
  59. package/dist/cjs/internal/multipart.js +1 -1
  60. package/dist/cjs/internal/multipart.js.map +1 -1
  61. package/dist/cjs/internal/worker.js +6 -7
  62. package/dist/cjs/internal/worker.js.map +1 -1
  63. package/dist/cjs/internal/workerRunner.js +3 -4
  64. package/dist/cjs/internal/workerRunner.js.map +1 -1
  65. package/dist/dts/Headers.d.ts +4 -6
  66. package/dist/dts/Headers.d.ts.map +1 -1
  67. package/dist/dts/HttpApi.d.ts +64 -140
  68. package/dist/dts/HttpApi.d.ts.map +1 -1
  69. package/dist/dts/HttpApiBuilder.d.ts +84 -167
  70. package/dist/dts/HttpApiBuilder.d.ts.map +1 -1
  71. package/dist/dts/HttpApiClient.d.ts +34 -11
  72. package/dist/dts/HttpApiClient.d.ts.map +1 -1
  73. package/dist/dts/HttpApiEndpoint.d.ts +119 -273
  74. package/dist/dts/HttpApiEndpoint.d.ts.map +1 -1
  75. package/dist/dts/HttpApiError.d.ts +5 -2
  76. package/dist/dts/HttpApiError.d.ts.map +1 -1
  77. package/dist/dts/HttpApiGroup.d.ts +96 -194
  78. package/dist/dts/HttpApiGroup.d.ts.map +1 -1
  79. package/dist/dts/HttpApiMiddleware.d.ts +231 -0
  80. package/dist/dts/HttpApiMiddleware.d.ts.map +1 -0
  81. package/dist/dts/HttpApiSchema.d.ts +6 -2
  82. package/dist/dts/HttpApiSchema.d.ts.map +1 -1
  83. package/dist/dts/HttpApiSecurity.d.ts +1 -1
  84. package/dist/dts/HttpApiSecurity.d.ts.map +1 -1
  85. package/dist/dts/HttpApiSwagger.d.ts +2 -2
  86. package/dist/dts/HttpApiSwagger.d.ts.map +1 -1
  87. package/dist/dts/HttpBody.d.ts +2 -2
  88. package/dist/dts/HttpBody.d.ts.map +1 -1
  89. package/dist/dts/HttpClientRequest.d.ts +2 -2
  90. package/dist/dts/HttpClientRequest.d.ts.map +1 -1
  91. package/dist/dts/HttpClientResponse.d.ts +3 -3
  92. package/dist/dts/HttpClientResponse.d.ts.map +1 -1
  93. package/dist/dts/HttpIncomingMessage.d.ts +3 -3
  94. package/dist/dts/HttpIncomingMessage.d.ts.map +1 -1
  95. package/dist/dts/HttpRouter.d.ts +3 -3
  96. package/dist/dts/HttpRouter.d.ts.map +1 -1
  97. package/dist/dts/HttpServer.d.ts +15 -0
  98. package/dist/dts/HttpServer.d.ts.map +1 -1
  99. package/dist/dts/HttpServerRequest.d.ts +3 -3
  100. package/dist/dts/HttpServerRequest.d.ts.map +1 -1
  101. package/dist/dts/HttpServerRespondable.d.ts.map +1 -1
  102. package/dist/dts/HttpServerResponse.d.ts +2 -2
  103. package/dist/dts/HttpServerResponse.d.ts.map +1 -1
  104. package/dist/dts/KeyValueStore.d.ts +2 -2
  105. package/dist/dts/KeyValueStore.d.ts.map +1 -1
  106. package/dist/dts/Multipart.d.ts +3 -3
  107. package/dist/dts/Multipart.d.ts.map +1 -1
  108. package/dist/dts/OpenApi.d.ts +17 -39
  109. package/dist/dts/OpenApi.d.ts.map +1 -1
  110. package/dist/dts/OpenApiJsonSchema.d.ts +10 -5
  111. package/dist/dts/OpenApiJsonSchema.d.ts.map +1 -1
  112. package/dist/dts/Transferable.d.ts +4 -1
  113. package/dist/dts/Transferable.d.ts.map +1 -1
  114. package/dist/dts/UrlParams.d.ts +3 -6
  115. package/dist/dts/UrlParams.d.ts.map +1 -1
  116. package/dist/dts/Worker.d.ts +7 -8
  117. package/dist/dts/Worker.d.ts.map +1 -1
  118. package/dist/dts/WorkerError.d.ts +1 -1
  119. package/dist/dts/WorkerError.d.ts.map +1 -1
  120. package/dist/dts/WorkerRunner.d.ts +2 -3
  121. package/dist/dts/WorkerRunner.d.ts.map +1 -1
  122. package/dist/dts/index.d.ts +4 -0
  123. package/dist/dts/index.d.ts.map +1 -1
  124. package/dist/dts/internal/httpRouter.d.ts.map +1 -1
  125. package/dist/esm/Headers.js +7 -2
  126. package/dist/esm/Headers.js.map +1 -1
  127. package/dist/esm/HttpApi.js +88 -77
  128. package/dist/esm/HttpApi.js.map +1 -1
  129. package/dist/esm/HttpApiBuilder.js +236 -244
  130. package/dist/esm/HttpApiBuilder.js.map +1 -1
  131. package/dist/esm/HttpApiClient.js +64 -59
  132. package/dist/esm/HttpApiClient.js.map +1 -1
  133. package/dist/esm/HttpApiEndpoint.js +73 -106
  134. package/dist/esm/HttpApiEndpoint.js.map +1 -1
  135. package/dist/esm/HttpApiError.js +3 -4
  136. package/dist/esm/HttpApiError.js.map +1 -1
  137. package/dist/esm/HttpApiGroup.js +102 -99
  138. package/dist/esm/HttpApiGroup.js.map +1 -1
  139. package/dist/esm/HttpApiMiddleware.js +56 -0
  140. package/dist/esm/HttpApiMiddleware.js.map +1 -0
  141. package/dist/esm/HttpApiSchema.js +31 -5
  142. package/dist/esm/HttpApiSchema.js.map +1 -1
  143. package/dist/esm/HttpApiSecurity.js +1 -1
  144. package/dist/esm/HttpApiSecurity.js.map +1 -1
  145. package/dist/esm/HttpApiSwagger.js +4 -2
  146. package/dist/esm/HttpApiSwagger.js.map +1 -1
  147. package/dist/esm/HttpBody.js.map +1 -1
  148. package/dist/esm/HttpIncomingMessage.js +4 -1
  149. package/dist/esm/HttpIncomingMessage.js.map +1 -1
  150. package/dist/esm/HttpServer.js +11 -0
  151. package/dist/esm/HttpServer.js.map +1 -1
  152. package/dist/esm/HttpServerRespondable.js +1 -1
  153. package/dist/esm/HttpServerRespondable.js.map +1 -1
  154. package/dist/esm/OpenApi.js +97 -59
  155. package/dist/esm/OpenApi.js.map +1 -1
  156. package/dist/esm/OpenApiJsonSchema.js +56 -46
  157. package/dist/esm/OpenApiJsonSchema.js.map +1 -1
  158. package/dist/esm/Transferable.js +2 -2
  159. package/dist/esm/Transferable.js.map +1 -1
  160. package/dist/esm/UrlParams.js +4 -1
  161. package/dist/esm/UrlParams.js.map +1 -1
  162. package/dist/esm/Worker.js.map +1 -1
  163. package/dist/esm/WorkerError.js +1 -4
  164. package/dist/esm/WorkerError.js.map +1 -1
  165. package/dist/esm/WorkerRunner.js.map +1 -1
  166. package/dist/esm/index.js +4 -0
  167. package/dist/esm/index.js.map +1 -1
  168. package/dist/esm/internal/httpBody.js +1 -1
  169. package/dist/esm/internal/httpBody.js.map +1 -1
  170. package/dist/esm/internal/httpClientRequest.js.map +1 -1
  171. package/dist/esm/internal/httpClientResponse.js +1 -1
  172. package/dist/esm/internal/httpClientResponse.js.map +1 -1
  173. package/dist/esm/internal/httpRouter.js +1 -1
  174. package/dist/esm/internal/httpRouter.js.map +1 -1
  175. package/dist/esm/internal/httpServer.js +6 -0
  176. package/dist/esm/internal/httpServer.js.map +1 -1
  177. package/dist/esm/internal/httpServerRequest.js +1 -1
  178. package/dist/esm/internal/httpServerRequest.js.map +1 -1
  179. package/dist/esm/internal/httpServerResponse.js.map +1 -1
  180. package/dist/esm/internal/keyValueStore.js +1 -1
  181. package/dist/esm/internal/keyValueStore.js.map +1 -1
  182. package/dist/esm/internal/multipart.js +1 -1
  183. package/dist/esm/internal/multipart.js.map +1 -1
  184. package/dist/esm/internal/worker.js +6 -7
  185. package/dist/esm/internal/worker.js.map +1 -1
  186. package/dist/esm/internal/workerRunner.js +3 -4
  187. package/dist/esm/internal/workerRunner.js.map +1 -1
  188. package/package.json +10 -3
  189. package/src/Headers.ts +12 -4
  190. package/src/HttpApi.ts +183 -258
  191. package/src/HttpApiBuilder.ts +532 -481
  192. package/src/HttpApiClient.ts +163 -112
  193. package/src/HttpApiEndpoint.ts +443 -564
  194. package/src/HttpApiError.ts +4 -6
  195. package/src/HttpApiGroup.ts +277 -325
  196. package/src/HttpApiMiddleware.ts +318 -0
  197. package/src/HttpApiSchema.ts +39 -2
  198. package/src/HttpApiSecurity.ts +1 -1
  199. package/src/HttpApiSwagger.ts +3 -3
  200. package/src/HttpBody.ts +2 -2
  201. package/src/HttpClientRequest.ts +2 -2
  202. package/src/HttpClientResponse.ts +3 -3
  203. package/src/HttpIncomingMessage.ts +3 -3
  204. package/src/HttpRouter.ts +3 -3
  205. package/src/HttpServer.ts +21 -0
  206. package/src/HttpServerRequest.ts +3 -3
  207. package/src/HttpServerRespondable.ts +1 -1
  208. package/src/HttpServerResponse.ts +2 -2
  209. package/src/KeyValueStore.ts +2 -2
  210. package/src/Multipart.ts +3 -3
  211. package/src/OpenApi.ts +113 -104
  212. package/src/OpenApiJsonSchema.ts +67 -53
  213. package/src/Transferable.ts +2 -2
  214. package/src/UrlParams.ts +3 -3
  215. package/src/Worker.ts +7 -8
  216. package/src/WorkerError.ts +1 -1
  217. package/src/WorkerRunner.ts +2 -3
  218. package/src/index.ts +5 -0
  219. package/src/internal/httpBody.ts +2 -2
  220. package/src/internal/httpClientRequest.ts +2 -2
  221. package/src/internal/httpClientResponse.ts +3 -3
  222. package/src/internal/httpRouter.ts +2 -2
  223. package/src/internal/httpServer.ts +13 -0
  224. package/src/internal/httpServerRequest.ts +3 -3
  225. package/src/internal/httpServerResponse.ts +2 -2
  226. package/src/internal/keyValueStore.ts +1 -1
  227. package/src/internal/multipart.ts +3 -3
  228. package/src/internal/worker.ts +6 -7
  229. package/src/internal/workerRunner.ts +3 -4
@@ -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 type { ManagedRuntime } from "effect/ManagedRuntime"
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, Mutable, NoInfer } from "effect/Types"
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
- * Build an `HttpApp` from an `HttpApi` instance, and serve it using an
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
- any
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.HttpApi.Service
105
+ Router | HttpApi.Api
110
106
  > = Effect.gen(function*() {
111
- const api = yield* HttpApi.HttpApi
112
- const router = yield* Router.router
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 { Etag, HttpApiBuilder, HttpMiddleware, HttpPlatform } from "@effect/platform"
135
- * import { NodeContext } from "@effect/platform-node"
136
- * import { Layer, ManagedRuntime } from "effect"
130
+ * import { HttpApi, HttpApiBuilder, HttpServer } from "@effect/platform"
131
+ * import { Layer } from "effect"
132
+ *
133
+ * class MyApi extends HttpApi.empty {}
137
134
  *
138
- * const ApiLive = HttpApiBuilder.api(HttpApi.empty)
135
+ * const MyApiLive = HttpApiBuilder.api(MyApi)
139
136
  *
140
- * const runtime = ManagedRuntime.make(
137
+ * const { dispose, handler } = HttpApiBuilder.toWebHandler(
141
138
  * Layer.mergeAll(
142
- * ApiLive,
143
- * HttpApiBuilder.Router.Live,
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 = <R, ER>(
154
- runtime: ManagedRuntime<R | HttpApi.HttpApi.Service | Router | HttpRouter.HttpRouter.DefaultServices, ER>,
155
- middleware?: (
156
- httpApp: HttpApp.Default
157
- ) => HttpApp.Default<never, R | HttpApi.HttpApi.Service | Router | HttpRouter.HttpRouter.DefaultServices>
158
- ): (request: Request) => Promise<Response> => {
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
- return (request) => handlerPromise.then((handler) => handler(request))
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, or partially handled, `HttpApiGroup`.
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.All = never
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<any, HttpApiEndpoint.HttpApiEndpoint.All, any, R>
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.All>(
391
+ const makeHandlers = <E, Provides, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Any>(
252
392
  options: {
253
- readonly group: HttpApiGroup.HttpApiGroup<any, HttpApiEndpoint.HttpApiEndpoint.All, any, R>
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 `HttpApiGroup`.
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 `HttpApiBuilder.handle` api.
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
- ApiErrorR,
278
- const Name extends Groups["identifier"],
279
- RH,
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, ApiErrorR>,
421
+ api: HttpApi.HttpApi<Groups, ApiError, ApiR>,
284
422
  groupName: Name,
285
423
  build: (
286
- handlers: Handlers<never, never, HttpApiGroup.HttpApiGroup.EndpointsWithName<Groups, Name>>
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.HttpApiGroup.Service<Name>,
292
- EX,
293
- RX | RH | HttpApiGroup.HttpApiGroup.ContextWithName<Groups, Name> | ApiErrorR
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 = Chunk.findFirst(api.groups, (group) => group.identifier === groupName)
299
- if (group._tag === "None") {
300
- throw new Error(`Group "${groupName}" not found in API`)
301
- }
302
- const result = build(makeHandlers({ group: group.value as any, handlers: Chunk.empty() }))
303
- const handlers = Effect.isEffect(result) ? (yield* result) : result
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
- if (item._tag === "Middleware") {
307
- for (const route of routes) {
308
- ;(route as Mutable<HttpRouter.Route<any, any>>).handler = item.middleware(route.handler as any)
309
- }
310
- } else {
311
- routes.push(handlerToRoute(
312
- item.endpoint,
313
- function(request) {
314
- return Effect.mapInputContext(
315
- item.handler(request),
316
- (input) => Context.merge(context, input)
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
- * Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
329
- *
330
- * @since 1.0.0
331
- * @category handlers
332
- */
333
- export const handle: {
334
- /**
335
- * Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
336
- *
337
- * @since 1.0.0
338
- * @category handlers
339
- */
340
- <Endpoints extends HttpApiEndpoint.HttpApiEndpoint.All, const Name extends Endpoints["name"], E, R>(
341
- name: Name,
342
- handler: HttpApiEndpoint.HttpApiEndpoint.HandlerWithName<Endpoints, Name, E, R>
343
- ): <EG, RG>(self: Handlers<EG, RG, Endpoints>) => Handlers<
344
- EG | Exclude<E, HttpApiEndpoint.HttpApiEndpoint.ErrorWithName<Endpoints, Name>> | HttpApiDecodeError,
345
- RG | HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<R>,
346
- HttpApiEndpoint.HttpApiEndpoint.ExcludeName<Endpoints, Name>
347
- >
348
- /**
349
- * Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
350
- *
351
- * @since 1.0.0
352
- * @category handlers
353
- */
354
- <Endpoints extends HttpApiEndpoint.HttpApiEndpoint.All, const Name extends Endpoints["name"], E, R>(
355
- name: Name,
356
- handler: HttpApiEndpoint.HttpApiEndpoint.HandlerResponseWithName<Endpoints, Name, E, R>,
357
- options: {
358
- readonly withFullResponse: true
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
- <EG, RG>(
373
- self: Handlers<EG, RG, Endpoints>
374
- ): Handlers<
375
- EG | Exclude<E, HttpApiEndpoint.HttpApiEndpoint.ErrorWithName<Endpoints, Name>> | HttpApiDecodeError,
376
- RG | HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<R>,
377
- HttpApiEndpoint.HttpApiEndpoint.ExcludeName<Endpoints, Name>
378
- > => {
379
- const o = Chunk.findFirst(self.group.endpoints, (endpoint) => endpoint.name === name)
380
- if (o._tag === "None") {
381
- throw new Error(`Endpoint "${name}" not found in group "${self.group.identifier}"`)
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
- const endpoint = o.value
384
- return makeHandlers({
385
- group: self.group,
386
- handlers: Chunk.append(self.handlers, {
387
- _tag: "Handler",
388
- endpoint,
389
- handler,
390
- withFullResponse: options?.withFullResponse === true
391
- }) as any
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
- * Add `HttpMiddleware` to a `Handlers` group.
397
- *
398
- * Any errors are required to have a corresponding schema in the API.
399
- * You can add middleware errors to an `HttpApiGroup` using the `HttpApiGroup.addError`
400
- * api.
401
- *
402
- * @since 1.0.0
403
- * @category middleware
404
- */
405
- export const middleware =
406
- <E, R, E1, R1>(middleware: Handlers.Middleware<E, R, E1, R1>) =>
407
- <Endpoints extends HttpApiEndpoint.HttpApiEndpoint.All>(
408
- self: Handlers<E, R, Endpoints>
409
- ): Handlers<E1, HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<R1>, Endpoints> =>
410
- makeHandlers<E1, HttpApiEndpoint.HttpApiEndpoint.ExcludeProvided<R1>, Endpoints>({
411
- ...self as any,
412
- handlers: Chunk.append(self.handlers, {
413
- _tag: "Middleware",
414
- middleware
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 middleware
726
+ * @category global
430
727
  */
431
- export declare namespace ApiMiddleware {
432
- /**
433
- * @since 1.0.0
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 middlewareLayer: {
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: ApiMiddleware.Fn<never> | Effect.Effect<ApiMiddleware.Fn<never>, EX, RX>,
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: ApiMiddleware.Fn<never, R> | Effect.Effect<ApiMiddleware.Fn<never, R>, EX, RX>,
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: ApiMiddleware.Fn<NoInfer<Error>> | Effect.Effect<ApiMiddleware.Fn<NoInfer<Error>>, EX, RX>,
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: ApiMiddleware.Fn<NoInfer<Error>, R> | Effect.Effect<ApiMiddleware.Fn<NoInfer<Error>, R>, EX, RX>,
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: ApiMiddleware.Fn<any, any> | Effect.Effect<ApiMiddleware.Fn<any, any>, any, any>,
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: ApiMiddleware.Fn<any, any> | Effect.Effect<ApiMiddleware.Fn<any, any>, any, any>,
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 middlewareLayerScoped: {
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<ApiMiddleware.Fn<never>, EX, RX>,
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<ApiMiddleware.Fn<never, R>, EX, RX>,
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<ApiMiddleware.Fn<NoInfer<Error>>, EX, RX>,
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<ApiMiddleware.Fn<NoInfer<Error>, R>, EX, RX>,
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: ApiMiddleware.Fn<any, any> | Effect.Effect<ApiMiddleware.Fn<any, any>, any, any>,
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: ApiMiddleware.Fn<any, any> | Effect.Effect<ApiMiddleware.Fn<any, any>, any, any>,
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> => middlewareLayer(HttpMiddleware.cors(options))
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.HttpApi.Service> =>
943
+ ): Layer.Layer<never, never, HttpApi.Api> =>
653
944
  Router.use((router) =>
654
945
  Effect.gen(function*() {
655
- const api = yield* HttpApi.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 middleware
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
- * ApiBuilder.handle(
1022
+ * handlers.handle(
742
1023
  * "authenticate",
743
- * (_) => ApiBuilder.securitySetCookie(security, "secret123")
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
- }