@effect/platform 0.68.5 → 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,14 +8,19 @@ 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";
14
+ import * as ManagedRuntime from "effect/ManagedRuntime";
15
15
  import * as Option from "effect/Option";
16
+ import * as ParseResult from "effect/ParseResult";
16
17
  import { pipeArguments } from "effect/Pipeable";
17
18
  import * as Redacted from "effect/Redacted";
19
+ import * as Schema from "effect/Schema";
18
20
  import { unify } from "effect/Unify";
19
21
  import * as HttpApi from "./HttpApi.js";
20
- import * as HttpApiEndpoint from "./HttpApiEndpoint.js";
21
22
  import { HttpApiDecodeError } from "./HttpApiError.js";
23
+ import * as HttpApiMiddleware from "./HttpApiMiddleware.js";
22
24
  import * as HttpApiSchema from "./HttpApiSchema.js";
23
25
  import * as HttpApp from "./HttpApp.js";
24
26
  import * as HttpMethod from "./HttpMethod.js";
@@ -35,6 +37,16 @@ import * as OpenApi from "./OpenApi.js";
35
37
  * @category router
36
38
  */
37
39
  export class Router extends /*#__PURE__*/HttpRouter.Tag("@effect/platform/HttpApiBuilder/Router")() {}
40
+ /**
41
+ * Create a top-level `HttpApi` layer.
42
+ *
43
+ * @since 1.0.0
44
+ * @category constructors
45
+ */
46
+ export const api = api => Layer.effect(HttpApi.Api, Effect.map(Effect.context(), context => ({
47
+ api: api,
48
+ context
49
+ })));
38
50
  /**
39
51
  * Build an `HttpApp` from an `HttpApi` instance, and serve it using an
40
52
  * `HttpServer`.
@@ -45,7 +57,7 @@ export class Router extends /*#__PURE__*/HttpRouter.Tag("@effect/platform/HttpAp
45
57
  * @since 1.0.0
46
58
  * @category constructors
47
59
  */
48
- export const serve = middleware => httpApp.pipe(Effect.map(HttpServer.serve(middleware)), Layer.unwrapEffect, Layer.provide(Router.Live));
60
+ export const serve = middleware => httpApp.pipe(Effect.map(app => HttpServer.serve(app, middleware)), Layer.unwrapEffect, Layer.provide(Router.Live));
49
61
  /**
50
62
  * Construct an `HttpApp` from an `HttpApi` instance.
51
63
  *
@@ -53,12 +65,16 @@ export const serve = middleware => httpApp.pipe(Effect.map(HttpServer.serve(midd
53
65
  * @category constructors
54
66
  */
55
67
  export const httpApp = /*#__PURE__*/Effect.gen(function* () {
56
- const api = yield* HttpApi.HttpApi;
57
- const router = yield* Router.router;
68
+ const {
69
+ api,
70
+ context
71
+ } = yield* HttpApi.Api;
72
+ const middleware = makeMiddlewareMap(api.middlewares, context);
73
+ const router = applyMiddleware(middleware, yield* Router.router);
58
74
  const apiMiddleware = yield* Effect.serviceOption(Middleware);
59
75
  const errorSchema = makeErrorSchema(api);
60
76
  const encodeError = Schema.encodeUnknown(errorSchema);
61
- return router.pipe(apiMiddleware._tag === "Some" ? apiMiddleware.value : identity, Effect.catchAll(error => Effect.matchEffect(encodeError(error), {
77
+ return router.pipe(apiMiddleware._tag === "Some" ? apiMiddleware.value : identity, Effect.catchAll(error => Effect.matchEffect(Effect.provide(encodeError(error), context), {
62
78
  onFailure: () => Effect.die(error),
63
79
  onSuccess: Effect.succeed
64
80
  })));
@@ -69,45 +85,40 @@ export const httpApp = /*#__PURE__*/Effect.gen(function* () {
69
85
  * @since 1.0.0
70
86
  * @category constructors
71
87
  * @example
72
- * import { HttpApi } from "@effect/platform"
73
- * import { Etag, HttpApiBuilder, HttpMiddleware, HttpPlatform } from "@effect/platform"
74
- * import { NodeContext } from "@effect/platform-node"
75
- * import { Layer, ManagedRuntime } from "effect"
88
+ * import { HttpApi, HttpApiBuilder, HttpServer } from "@effect/platform"
89
+ * import { Layer } from "effect"
90
+ *
91
+ * class MyApi extends HttpApi.empty {}
76
92
  *
77
- * const ApiLive = HttpApiBuilder.api(HttpApi.empty)
93
+ * const MyApiLive = HttpApiBuilder.api(MyApi)
78
94
  *
79
- * const runtime = ManagedRuntime.make(
95
+ * const { dispose, handler } = HttpApiBuilder.toWebHandler(
80
96
  * Layer.mergeAll(
81
- * ApiLive,
82
- * HttpApiBuilder.Router.Live,
83
- * HttpPlatform.layer,
84
- * Etag.layerWeak
85
- * ).pipe(
86
- * Layer.provideMerge(NodeContext.layer)
97
+ * MyApiLive,
98
+ * HttpServer.layerContext
87
99
  * )
88
100
  * )
89
- *
90
- * const handler = HttpApiBuilder.toWebHandler(runtime, HttpMiddleware.logger)
91
101
  */
92
- export const toWebHandler = (runtime, middleware) => {
102
+ export const toWebHandler = (layer, options) => {
103
+ const runtime = ManagedRuntime.make(Layer.merge(layer, Router.Live), options?.memoMap);
104
+ let handlerCached;
93
105
  const handlerPromise = httpApp.pipe(Effect.bindTo("httpApp"), Effect.bind("runtime", () => runtime.runtimeEffect), Effect.map(({
94
106
  httpApp,
95
107
  runtime
96
- }) => HttpApp.toWebHandlerRuntime(runtime)(middleware ? middleware(httpApp) : httpApp)), runtime.runPromise);
97
- return request => handlerPromise.then(handler => handler(request));
108
+ }) => HttpApp.toWebHandlerRuntime(runtime)(options?.middleware ? options.middleware(httpApp) : httpApp)), Effect.tap(handler => {
109
+ handlerCached = handler;
110
+ }), runtime.runPromise);
111
+ function handler(request) {
112
+ if (handlerCached !== undefined) {
113
+ return handlerCached(request);
114
+ }
115
+ return handlerPromise.then(handler => handler(request));
116
+ }
117
+ return {
118
+ handler,
119
+ dispose: runtime.dispose
120
+ };
98
121
  };
99
- /**
100
- * Build a root level `Layer` from an `HttpApi` instance.
101
- *
102
- * The `Layer` will provide the `HttpApi` service, and will require the
103
- * implementation for all the `HttpApiGroup`'s contained in the `HttpApi`.
104
- *
105
- * The resulting `Layer` can be provided to the `HttpApiBuilder.serve` layer.
106
- *
107
- * @since 1.0.0
108
- * @category constructors
109
- */
110
- export const api = self => Layer.succeed(HttpApi.HttpApi, self);
111
122
  /**
112
123
  * @since 1.0.0
113
124
  * @category handlers
@@ -119,6 +130,28 @@ const HandlersProto = {
119
130
  },
120
131
  pipe() {
121
132
  return pipeArguments(this, arguments);
133
+ },
134
+ handle(name, handler) {
135
+ const endpoint = HashMap.unsafeGet(this.group.endpoints, name);
136
+ return makeHandlers({
137
+ group: this.group,
138
+ handlers: Chunk.append(this.handlers, {
139
+ endpoint,
140
+ handler,
141
+ withFullResponse: false
142
+ })
143
+ });
144
+ },
145
+ handleRaw(name, handler) {
146
+ const endpoint = HashMap.unsafeGet(this.group.endpoints, name);
147
+ return makeHandlers({
148
+ group: this.group,
149
+ handlers: Chunk.append(this.handlers, {
150
+ endpoint,
151
+ handler,
152
+ withFullResponse: true
153
+ })
154
+ });
122
155
  }
123
156
  };
124
157
  const makeHandlers = options => {
@@ -128,81 +161,186 @@ const makeHandlers = options => {
128
161
  return self;
129
162
  };
130
163
  /**
131
- * Create a `Layer` that will implement all the endpoints in an `HttpApiGroup`.
164
+ * Create a `Layer` that will implement all the endpoints in an `HttpApi`.
132
165
  *
133
166
  * An unimplemented `Handlers` instance is passed to the `build` function, which
134
167
  * you can use to add handlers to the group.
135
168
  *
136
- * You can implement endpoints using the `HttpApiBuilder.handle` api.
169
+ * You can implement endpoints using the `handlers.handle` api.
137
170
  *
138
171
  * @since 1.0.0
139
172
  * @category handlers
140
173
  */
141
174
  export const group = (api, groupName, build) => Router.use(router => Effect.gen(function* () {
142
175
  const context = yield* Effect.context();
143
- const group = Chunk.findFirst(api.groups, group => group.identifier === groupName);
144
- if (group._tag === "None") {
145
- throw new Error(`Group "${groupName}" not found in API`);
146
- }
176
+ const group = HashMap.unsafeGet(api.groups, groupName);
147
177
  const result = build(makeHandlers({
148
- group: group.value,
178
+ group,
149
179
  handlers: Chunk.empty()
150
180
  }));
151
181
  const handlers = Effect.isEffect(result) ? yield* result : result;
182
+ const groupMiddleware = makeMiddlewareMap(group.middlewares, context);
152
183
  const routes = [];
153
184
  for (const item of handlers.handlers) {
154
- if (item._tag === "Middleware") {
155
- for (const route of routes) {
156
- ;
157
- route.handler = item.middleware(route.handler);
185
+ const middleware = makeMiddlewareMap(item.endpoint.middlewares, context, groupMiddleware);
186
+ routes.push(handlerToRoute(item.endpoint, middleware, function (request) {
187
+ return Effect.mapInputContext(item.handler(request), input => Context.merge(context, input));
188
+ }, item.withFullResponse));
189
+ }
190
+ yield* router.concat(HttpRouter.fromIterable(routes));
191
+ }));
192
+ // internal
193
+ const requestPayload = (request, urlParams, isMultipart) => HttpMethod.hasBody(request.method) ? isMultipart ? Effect.orDie(request.multipart) : Effect.orDie(request.json) : Effect.succeed(urlParams);
194
+ const makeMiddlewareMap = (middleware, context, initial) => {
195
+ const map = new Map(initial);
196
+ HashSet.forEach(middleware, tag => {
197
+ map.set(tag.key, {
198
+ tag,
199
+ effect: Context.unsafeGet(context, tag)
200
+ });
201
+ });
202
+ return map;
203
+ };
204
+ const handlerToRoute = (endpoint_, middleware, handler, isFullResponse) => {
205
+ const endpoint = endpoint_;
206
+ const decodePath = Option.map(endpoint.pathSchema, Schema.decodeUnknown);
207
+ const isMultipart = endpoint.payloadSchema.pipe(Option.map(schema => HttpApiSchema.getMultipart(schema.ast)), Option.getOrElse(() => false));
208
+ const decodePayload = Option.map(endpoint.payloadSchema, Schema.decodeUnknown);
209
+ const decodeHeaders = Option.map(endpoint.headersSchema, Schema.decodeUnknown);
210
+ const decodeUrlParams = Option.map(endpoint.urlParamsSchema, Schema.decodeUnknown);
211
+ const encodeSuccess = Schema.encode(makeSuccessSchema(endpoint.successSchema));
212
+ return HttpRouter.makeRoute(endpoint.method, endpoint.path, applyMiddleware(middleware, Effect.withFiberRuntime(fiber => {
213
+ const context = fiber.getFiberRef(FiberRef.currentContext);
214
+ const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest);
215
+ const routeContext = Context.unsafeGet(context, HttpRouter.RouteContext);
216
+ const urlParams = Context.unsafeGet(context, HttpServerRequest.ParsedSearchParams);
217
+ return (decodePath._tag === "Some" ? decodePath.value(routeContext.params) : Effect.succeed(routeContext.params)).pipe(Effect.bindTo("pathParams"), decodePayload._tag === "Some" ? Effect.bind("payload", _ => Effect.flatMap(requestPayload(request, urlParams, isMultipart), decodePayload.value)) : identity, decodeHeaders._tag === "Some" ? Effect.bind("headers", _ => decodeHeaders.value(request.headers)) : identity, decodeUrlParams._tag === "Some" ? Effect.bind("urlParams", _ => decodeUrlParams.value(urlParams)) : identity, Effect.flatMap(input => {
218
+ const request = {
219
+ path: input.pathParams
220
+ };
221
+ if ("payload" in input) {
222
+ request.payload = input.payload;
223
+ }
224
+ if ("headers" in input) {
225
+ request.headers = input.headers;
226
+ }
227
+ if ("urlParams" in input) {
228
+ request.urlParams = input.urlParams;
158
229
  }
230
+ return handler(request);
231
+ }), isFullResponse ? identity : Effect.flatMap(encodeSuccess), Effect.catchIf(ParseResult.isParseError, HttpApiDecodeError.refailParseError));
232
+ })));
233
+ };
234
+ const applyMiddleware = (middleware, handler) => {
235
+ for (const entry of middleware.values()) {
236
+ const effect = HttpApiMiddleware.SecurityTypeId in entry.tag ? makeSecurityMiddleware(entry) : entry.effect;
237
+ if (entry.tag.optional) {
238
+ const previous = handler;
239
+ handler = Effect.matchEffect(effect, {
240
+ onFailure: () => previous,
241
+ onSuccess: entry.tag.provides !== undefined ? value => Effect.provideService(previous, entry.tag.provides, value) : _ => previous
242
+ });
159
243
  } else {
160
- routes.push(handlerToRoute(item.endpoint, function (request) {
161
- return Effect.mapInputContext(item.handler(request), input => Context.merge(context, input));
162
- }, item.withFullResponse));
244
+ handler = entry.tag.provides !== undefined ? Effect.provideServiceEffect(handler, entry.tag.provides, effect) : Effect.zipRight(effect, handler);
163
245
  }
164
246
  }
165
- yield* router.concat(HttpRouter.fromIterable(routes));
166
- }));
167
- /**
168
- * Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
169
- *
170
- * @since 1.0.0
171
- * @category handlers
172
- */
173
- export const handle = (name, handler, options) => self => {
174
- const o = Chunk.findFirst(self.group.endpoints, endpoint => endpoint.name === name);
175
- if (o._tag === "None") {
176
- throw new Error(`Endpoint "${name}" not found in group "${self.group.identifier}"`);
247
+ return handler;
248
+ };
249
+ const securityMiddlewareCache = /*#__PURE__*/globalValue("securityMiddlewareCache", () => new WeakMap());
250
+ const makeSecurityMiddleware = entry => {
251
+ if (securityMiddlewareCache.has(entry.tag)) {
252
+ return securityMiddlewareCache.get(entry.tag);
253
+ }
254
+ let effect;
255
+ for (const [key, security] of Object.entries(entry.tag.security)) {
256
+ const decode = securityDecode(security);
257
+ const handler = entry.effect[key];
258
+ const middleware = Effect.flatMap(decode, handler);
259
+ effect = effect === undefined ? middleware : Effect.catchAll(effect, () => middleware);
177
260
  }
178
- const endpoint = o.value;
179
- return makeHandlers({
180
- group: self.group,
181
- handlers: Chunk.append(self.handlers, {
182
- _tag: "Handler",
183
- endpoint,
184
- handler,
185
- withFullResponse: options?.withFullResponse === true
186
- })
261
+ if (effect === undefined) {
262
+ effect = Effect.void;
263
+ }
264
+ securityMiddlewareCache.set(entry.tag, effect);
265
+ return effect;
266
+ };
267
+ const responseSchema = /*#__PURE__*/Schema.declare(HttpServerResponse.isServerResponse);
268
+ const makeSuccessSchema = schema => {
269
+ const schemas = new Set();
270
+ HttpApiSchema.deunionize(schemas, schema);
271
+ return Schema.Union(...Array.from(schemas, toResponseSuccess));
272
+ };
273
+ const makeErrorSchema = api => {
274
+ const schemas = new Set();
275
+ HttpApiSchema.deunionize(schemas, api.errorSchema);
276
+ HashMap.forEach(api.groups, group => {
277
+ HashMap.forEach(group.endpoints, endpoint => {
278
+ HttpApiSchema.deunionize(schemas, endpoint.errorSchema);
279
+ });
280
+ HttpApiSchema.deunionize(schemas, group.errorSchema);
187
281
  });
282
+ return Schema.Union(...Array.from(schemas, toResponseError));
188
283
  };
189
- /**
190
- * Add `HttpMiddleware` to a `Handlers` group.
191
- *
192
- * Any errors are required to have a corresponding schema in the API.
193
- * You can add middleware errors to an `HttpApiGroup` using the `HttpApiGroup.addError`
194
- * api.
195
- *
196
- * @since 1.0.0
197
- * @category middleware
198
- */
199
- export const middleware = middleware => self => makeHandlers({
200
- ...self,
201
- handlers: Chunk.append(self.handlers, {
202
- _tag: "Middleware",
203
- middleware
204
- })
205
- });
284
+ const decodeForbidden = (_, __, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, _, "Encode only schema"));
285
+ const toResponseSchema = getStatus => {
286
+ const cache = new WeakMap();
287
+ const schemaToResponse = (data, _, ast) => {
288
+ const isEmpty = HttpApiSchema.isVoid(ast.to);
289
+ const status = getStatus(ast.to);
290
+ if (isEmpty) {
291
+ return HttpServerResponse.empty({
292
+ status
293
+ });
294
+ }
295
+ const encoding = HttpApiSchema.getEncoding(ast.to);
296
+ switch (encoding.kind) {
297
+ case "Json":
298
+ {
299
+ return Effect.mapError(HttpServerResponse.json(data, {
300
+ status,
301
+ contentType: encoding.contentType
302
+ }), error => new ParseResult.Type(ast, error, "Could not encode to JSON"));
303
+ }
304
+ case "Text":
305
+ {
306
+ return ParseResult.succeed(HttpServerResponse.text(data, {
307
+ status,
308
+ contentType: encoding.contentType
309
+ }));
310
+ }
311
+ case "Uint8Array":
312
+ {
313
+ return ParseResult.succeed(HttpServerResponse.uint8Array(data, {
314
+ status,
315
+ contentType: encoding.contentType
316
+ }));
317
+ }
318
+ case "UrlParams":
319
+ {
320
+ return ParseResult.succeed(HttpServerResponse.urlParams(data, {
321
+ status,
322
+ contentType: encoding.contentType
323
+ }));
324
+ }
325
+ }
326
+ };
327
+ return schema => {
328
+ if (cache.has(schema.ast)) {
329
+ return cache.get(schema.ast);
330
+ }
331
+ const transform = Schema.transformOrFail(responseSchema, schema, {
332
+ decode: decodeForbidden,
333
+ encode: schemaToResponse
334
+ });
335
+ cache.set(transform.ast, transform);
336
+ return transform;
337
+ };
338
+ };
339
+ const toResponseSuccess = /*#__PURE__*/toResponseSchema(HttpApiSchema.getStatusSuccessAST);
340
+ const toResponseError = /*#__PURE__*/toResponseSchema(HttpApiSchema.getStatusErrorAST);
341
+ // ----------------------------------------------------------------------------
342
+ // Global middleware
343
+ // ----------------------------------------------------------------------------
206
344
  /**
207
345
  * @since 1.0.0
208
346
  * @category middleware
@@ -222,7 +360,7 @@ const middlewareAddNoContext = middleware => Effect.map(Effect.serviceOption(Mid
222
360
  * @since 1.0.0
223
361
  * @category middleware
224
362
  */
225
- export const middlewareLayer = (...args) => {
363
+ export const middleware = (...args) => {
226
364
  const apiFirst = HttpApi.isHttpApi(args[0]);
227
365
  const withContext = apiFirst ? args[2]?.withContext === true : args[1]?.withContext === true;
228
366
  const add = withContext ? middlewareAdd : middlewareAddNoContext;
@@ -236,7 +374,7 @@ export const middlewareLayer = (...args) => {
236
374
  * @since 1.0.0
237
375
  * @category middleware
238
376
  */
239
- export const middlewareLayerScoped = (...args) => {
377
+ export const middlewareScoped = (...args) => {
240
378
  const apiFirst = HttpApi.isHttpApi(args[0]);
241
379
  const withContext = apiFirst ? args[2]?.withContext === true : args[1]?.withContext === true;
242
380
  const add = withContext ? middlewareAdd : middlewareAddNoContext;
@@ -249,7 +387,7 @@ export const middlewareLayerScoped = (...args) => {
249
387
  * @since 1.0.0
250
388
  * @category middleware
251
389
  */
252
- export const middlewareCors = options => middlewareLayer(HttpMiddleware.cors(options));
390
+ export const middlewareCors = options => middleware(HttpMiddleware.cors(options));
253
391
  /**
254
392
  * A middleware that adds an openapi.json endpoint to the API.
255
393
  *
@@ -257,7 +395,9 @@ export const middlewareCors = options => middlewareLayer(HttpMiddleware.cors(opt
257
395
  * @category middleware
258
396
  */
259
397
  export const middlewareOpenApi = options => Router.use(router => Effect.gen(function* () {
260
- const api = yield* HttpApi.HttpApi;
398
+ const {
399
+ api
400
+ } = yield* HttpApi.Api;
261
401
  const spec = OpenApi.fromApi(api);
262
402
  const response = yield* HttpServerResponse.json(spec).pipe(Effect.orDie);
263
403
  yield* router.get(options?.path ?? "/openapi.json", Effect.succeed(response));
@@ -265,7 +405,7 @@ export const middlewareOpenApi = options => Router.use(router => Effect.gen(func
265
405
  const bearerLen = `Bearer `.length;
266
406
  /**
267
407
  * @since 1.0.0
268
- * @category middleware
408
+ * @category security
269
409
  */
270
410
  export const securityDecode = self => {
271
411
  switch (self._tag) {
@@ -312,9 +452,9 @@ export const securityDecode = self => {
312
452
  * You can use this api before returning a response from an endpoint handler.
313
453
  *
314
454
  * ```ts
315
- * ApiBuilder.handle(
455
+ * handlers.handle(
316
456
  * "authenticate",
317
- * (_) => ApiBuilder.securitySetCookie(security, "secret123")
457
+ * (_) => HttpApiBuilder.securitySetCookie(security, "secret123")
318
458
  * )
319
459
  * ```
320
460
  *
@@ -329,152 +469,4 @@ export const securitySetCookie = (self, value, options) => {
329
469
  ...options
330
470
  })));
331
471
  };
332
- /**
333
- * Make a middleware from an `HttpApiSecurity` instance, that can be used when
334
- * constructing a `Handlers` group.
335
- *
336
- * @since 1.0.0
337
- * @category middleware
338
- * @example
339
- * import { HttpApiBuilder, HttpApiSecurity } from "@effect/platform"
340
- * import { Schema } from "@effect/schema"
341
- * import { Context, Effect, Redacted } from "effect"
342
- *
343
- * class User extends Schema.Class<User>("User")({
344
- * id: Schema.Number
345
- * }) {}
346
- *
347
- * class CurrentUser extends Context.Tag("CurrentUser")<CurrentUser, User>() {}
348
- *
349
- * class Accounts extends Context.Tag("Accounts")<Accounts, {
350
- * readonly findUserByAccessToken: (accessToken: string) => Effect.Effect<User>
351
- * }>() {}
352
- *
353
- * const securityMiddleware = Effect.gen(function*() {
354
- * const accounts = yield* Accounts
355
- * return HttpApiBuilder.middlewareSecurity(
356
- * HttpApiSecurity.bearer,
357
- * CurrentUser,
358
- * (token) => accounts.findUserByAccessToken(Redacted.value(token))
359
- * )
360
- * })
361
- */
362
- export const middlewareSecurity = (self, tag, f) => middleware(Effect.provideServiceEffect(tag, Effect.flatMap(securityDecode(self), f)));
363
- /**
364
- * Make a middleware from an `HttpApiSecurity` instance, that can be used when
365
- * constructing a `Handlers` group.
366
- *
367
- * This version does not supply any context to the handlers.
368
- *
369
- * @since 1.0.0
370
- * @category middleware
371
- */
372
- export const middlewareSecurityVoid = (self, f) => middleware(httpApp => securityDecode(self).pipe(Effect.flatMap(f), Effect.zipRight(httpApp)));
373
- // internal
374
- const requestPayload = (request, urlParams, isMultipart) => HttpMethod.hasBody(request.method) ? isMultipart ? Effect.orDie(request.multipart) : Effect.orDie(request.json) : Effect.succeed(urlParams);
375
- const handlerToRoute = (endpoint, handler, isFullResponse) => {
376
- const decodePath = Option.map(endpoint.pathSchema, Schema.decodeUnknown);
377
- const isMultipart = endpoint.payloadSchema.pipe(Option.map(schema => HttpApiSchema.getMultipart(schema.ast)), Option.getOrElse(() => false));
378
- const decodePayload = Option.map(endpoint.payloadSchema, Schema.decodeUnknown);
379
- const decodeHeaders = Option.map(endpoint.headersSchema, Schema.decodeUnknown);
380
- const encoding = HttpApiSchema.getEncoding(endpoint.successSchema.ast);
381
- const successStatus = HttpApiSchema.getStatusSuccess(endpoint.successSchema);
382
- const encodeSuccess = Option.map(HttpApiEndpoint.schemaSuccess(endpoint), schema => {
383
- const encode = Schema.encodeUnknown(schema);
384
- switch (encoding.kind) {
385
- case "Json":
386
- {
387
- return body => Effect.orDie(Effect.flatMap(encode(body), json => HttpServerResponse.json(json, {
388
- status: successStatus,
389
- contentType: encoding.contentType
390
- })));
391
- }
392
- case "Text":
393
- {
394
- return body => Effect.map(Effect.orDie(encode(body)), text => HttpServerResponse.text(text, {
395
- status: successStatus,
396
- contentType: encoding.contentType
397
- }));
398
- }
399
- case "Uint8Array":
400
- {
401
- return body => Effect.map(Effect.orDie(encode(body)), data => HttpServerResponse.uint8Array(data, {
402
- status: successStatus,
403
- contentType: encoding.contentType
404
- }));
405
- }
406
- case "UrlParams":
407
- {
408
- return body => Effect.map(Effect.orDie(encode(body)), params => HttpServerResponse.urlParams(params, {
409
- status: successStatus,
410
- contentType: encoding.contentType
411
- }));
412
- }
413
- }
414
- });
415
- return HttpRouter.makeRoute(endpoint.method, endpoint.path, Effect.withFiberRuntime(fiber => {
416
- const context = fiber.getFiberRef(FiberRef.currentContext);
417
- const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest);
418
- const routeContext = Context.unsafeGet(context, HttpRouter.RouteContext);
419
- const urlParams = Context.unsafeGet(context, HttpServerRequest.ParsedSearchParams);
420
- return (decodePath._tag === "Some" ? Effect.catchAll(decodePath.value(routeContext.params), HttpApiDecodeError.refailParseError) : Effect.succeed(routeContext.params)).pipe(Effect.bindTo("pathParams"), decodePayload._tag === "Some" ? Effect.bind("payload", _ => requestPayload(request, urlParams, isMultipart).pipe(Effect.orDie, Effect.flatMap(raw => Effect.catchAll(decodePayload.value(raw), HttpApiDecodeError.refailParseError)))) : identity, decodeHeaders._tag === "Some" ? Effect.bind("headers", _ => Effect.orDie(decodeHeaders.value(request.headers))) : identity, Effect.flatMap(input => {
421
- const request = {
422
- path: input.pathParams
423
- };
424
- if ("payload" in input) {
425
- request.payload = input.payload;
426
- }
427
- if ("headers" in input) {
428
- request.headers = input.headers;
429
- }
430
- return handler(request);
431
- }), isFullResponse ? identity : encodeSuccess._tag === "Some" ? Effect.flatMap(encodeSuccess.value) : Effect.as(HttpServerResponse.empty({
432
- status: successStatus
433
- })));
434
- }));
435
- };
436
- const astCache = /*#__PURE__*/globalValue("@effect/platform/HttpApiBuilder/astCache", () => new WeakMap());
437
- const makeErrorSchema = api => {
438
- const schemas = new Set();
439
- function processSchema(schema) {
440
- if (astCache.has(schema.ast)) {
441
- schemas.add(astCache.get(schema.ast));
442
- return;
443
- }
444
- const ast = schema.ast;
445
- if (ast._tag === "Union") {
446
- for (const astType of ast.types) {
447
- const errorSchema = Schema.make(astType).annotations({
448
- ...ast.annotations,
449
- ...astType.annotations
450
- });
451
- astCache.set(astType, errorSchema);
452
- schemas.add(errorSchema);
453
- }
454
- } else {
455
- astCache.set(ast, schema);
456
- schemas.add(schema);
457
- }
458
- }
459
- processSchema(api.errorSchema);
460
- for (const group of api.groups) {
461
- for (const endpoint of group.endpoints) {
462
- processSchema(endpoint.errorSchema);
463
- }
464
- processSchema(group.errorSchema);
465
- }
466
- return Schema.Union(...[...schemas].map(schema => {
467
- const status = HttpApiSchema.getStatusError(schema);
468
- const encoded = AST.encodedAST(schema.ast);
469
- const isEmpty = encoded._tag === "VoidKeyword";
470
- return Schema.transformOrFail(Schema.Any, schema, {
471
- decode: (_, __, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, _, "Encode only schema")),
472
- encode: (error, _, ast) => isEmpty ? HttpServerResponse.empty({
473
- status
474
- }) : HttpServerResponse.json(error, {
475
- status
476
- }).pipe(Effect.mapError(error => new ParseResult.Type(ast, error, "Could not encode to JSON")))
477
- });
478
- }));
479
- };
480
472
  //# sourceMappingURL=HttpApiBuilder.js.map