@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.
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 +245 -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 +86 -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 +228 -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 +238 -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 +534 -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 +317 -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,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 { 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
+ * // 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 = <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> => {
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
- return (request) => handlerPromise.then((handler) => handler(request))
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, or partially handled, `HttpApiGroup`.
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.All = never
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<any, HttpApiEndpoint.HttpApiEndpoint.All, any, R>
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.All>(
393
+ const makeHandlers = <E, Provides, R, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Any>(
252
394
  options: {
253
- readonly group: HttpApiGroup.HttpApiGroup<any, HttpApiEndpoint.HttpApiEndpoint.All, any, R>
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 `HttpApiGroup`.
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 `HttpApiBuilder.handle` api.
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
- ApiErrorR,
278
- const Name extends Groups["identifier"],
279
- RH,
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, ApiErrorR>,
423
+ api: HttpApi.HttpApi<Groups, ApiError, ApiR>,
284
424
  groupName: Name,
285
425
  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>
426
+ handlers: Handlers.FromGroup<ApiError, ApiR, HttpApiGroup.HttpApiGroup.WithName<Groups, Name>>
427
+ ) => Handlers.ValidateReturn<Return>
290
428
  ): Layer.Layer<
291
- HttpApiGroup.HttpApiGroup.Service<Name>,
292
- EX,
293
- RX | RH | HttpApiGroup.HttpApiGroup.ContextWithName<Groups, Name> | ApiErrorR
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 = 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
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
- 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
- }
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
- * 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
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
- <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}"`)
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
- 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
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
- * 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
- })
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 middleware
728
+ * @category global
430
729
  */
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
- }
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 middlewareLayer: {
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: ApiMiddleware.Fn<never> | Effect.Effect<ApiMiddleware.Fn<never>, EX, RX>,
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: ApiMiddleware.Fn<never, R> | Effect.Effect<ApiMiddleware.Fn<never, R>, EX, RX>,
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: ApiMiddleware.Fn<NoInfer<Error>> | Effect.Effect<ApiMiddleware.Fn<NoInfer<Error>>, EX, RX>,
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: ApiMiddleware.Fn<NoInfer<Error>, R> | Effect.Effect<ApiMiddleware.Fn<NoInfer<Error>, R>, EX, RX>,
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: ApiMiddleware.Fn<any, any> | Effect.Effect<ApiMiddleware.Fn<any, any>, any, any>,
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: ApiMiddleware.Fn<any, any> | Effect.Effect<ApiMiddleware.Fn<any, any>, any, any>,
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 middlewareLayerScoped: {
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<ApiMiddleware.Fn<never>, EX, RX>,
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<ApiMiddleware.Fn<never, R>, EX, RX>,
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<ApiMiddleware.Fn<NoInfer<Error>>, EX, RX>,
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<ApiMiddleware.Fn<NoInfer<Error>, R>, EX, RX>,
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: ApiMiddleware.Fn<any, any> | Effect.Effect<ApiMiddleware.Fn<any, any>, any, any>,
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: ApiMiddleware.Fn<any, any> | Effect.Effect<ApiMiddleware.Fn<any, any>, any, any>,
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> => middlewareLayer(HttpMiddleware.cors(options))
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.HttpApi.Service> =>
945
+ ): Layer.Layer<never, never, HttpApi.Api> =>
653
946
  Router.use((router) =>
654
947
  Effect.gen(function*() {
655
- const api = yield* HttpApi.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 middleware
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
- * ApiBuilder.handle(
1024
+ * handlers.handle(
742
1025
  * "authenticate",
743
- * (_) => ApiBuilder.securitySetCookie(security, "secret123")
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
- }