@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,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,42 @@ 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
+ * // you could also use NodeHttpServer.layerContext, depending on your
99
+ * // server's platform
100
+ * HttpServer.layerContext
87
101
  * )
88
102
  * )
89
- *
90
- * const handler = HttpApiBuilder.toWebHandler(runtime, HttpMiddleware.logger)
91
103
  */
92
- export const toWebHandler = (runtime, middleware) => {
104
+ export const toWebHandler = (layer, options) => {
105
+ const runtime = ManagedRuntime.make(Layer.merge(layer, Router.Live), options?.memoMap);
106
+ let handlerCached;
93
107
  const handlerPromise = httpApp.pipe(Effect.bindTo("httpApp"), Effect.bind("runtime", () => runtime.runtimeEffect), Effect.map(({
94
108
  httpApp,
95
109
  runtime
96
- }) => HttpApp.toWebHandlerRuntime(runtime)(middleware ? middleware(httpApp) : httpApp)), runtime.runPromise);
97
- return request => handlerPromise.then(handler => handler(request));
110
+ }) => HttpApp.toWebHandlerRuntime(runtime)(options?.middleware ? options.middleware(httpApp) : httpApp)), Effect.tap(handler => {
111
+ handlerCached = handler;
112
+ }), runtime.runPromise);
113
+ function handler(request) {
114
+ if (handlerCached !== undefined) {
115
+ return handlerCached(request);
116
+ }
117
+ return handlerPromise.then(handler => handler(request));
118
+ }
119
+ return {
120
+ handler,
121
+ dispose: runtime.dispose
122
+ };
98
123
  };
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
124
  /**
112
125
  * @since 1.0.0
113
126
  * @category handlers
@@ -119,6 +132,28 @@ const HandlersProto = {
119
132
  },
120
133
  pipe() {
121
134
  return pipeArguments(this, arguments);
135
+ },
136
+ handle(name, handler) {
137
+ const endpoint = HashMap.unsafeGet(this.group.endpoints, name);
138
+ return makeHandlers({
139
+ group: this.group,
140
+ handlers: Chunk.append(this.handlers, {
141
+ endpoint,
142
+ handler,
143
+ withFullResponse: false
144
+ })
145
+ });
146
+ },
147
+ handleRaw(name, handler) {
148
+ const endpoint = HashMap.unsafeGet(this.group.endpoints, name);
149
+ return makeHandlers({
150
+ group: this.group,
151
+ handlers: Chunk.append(this.handlers, {
152
+ endpoint,
153
+ handler,
154
+ withFullResponse: true
155
+ })
156
+ });
122
157
  }
123
158
  };
124
159
  const makeHandlers = options => {
@@ -128,81 +163,186 @@ const makeHandlers = options => {
128
163
  return self;
129
164
  };
130
165
  /**
131
- * Create a `Layer` that will implement all the endpoints in an `HttpApiGroup`.
166
+ * Create a `Layer` that will implement all the endpoints in an `HttpApi`.
132
167
  *
133
168
  * An unimplemented `Handlers` instance is passed to the `build` function, which
134
169
  * you can use to add handlers to the group.
135
170
  *
136
- * You can implement endpoints using the `HttpApiBuilder.handle` api.
171
+ * You can implement endpoints using the `handlers.handle` api.
137
172
  *
138
173
  * @since 1.0.0
139
174
  * @category handlers
140
175
  */
141
176
  export const group = (api, groupName, build) => Router.use(router => Effect.gen(function* () {
142
177
  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
- }
178
+ const group = HashMap.unsafeGet(api.groups, groupName);
147
179
  const result = build(makeHandlers({
148
- group: group.value,
180
+ group,
149
181
  handlers: Chunk.empty()
150
182
  }));
151
183
  const handlers = Effect.isEffect(result) ? yield* result : result;
184
+ const groupMiddleware = makeMiddlewareMap(group.middlewares, context);
152
185
  const routes = [];
153
186
  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);
187
+ const middleware = makeMiddlewareMap(item.endpoint.middlewares, context, groupMiddleware);
188
+ routes.push(handlerToRoute(item.endpoint, middleware, function (request) {
189
+ return Effect.mapInputContext(item.handler(request), input => Context.merge(context, input));
190
+ }, item.withFullResponse));
191
+ }
192
+ yield* router.concat(HttpRouter.fromIterable(routes));
193
+ }));
194
+ // internal
195
+ const requestPayload = (request, urlParams, isMultipart) => HttpMethod.hasBody(request.method) ? isMultipart ? Effect.orDie(request.multipart) : Effect.orDie(request.json) : Effect.succeed(urlParams);
196
+ const makeMiddlewareMap = (middleware, context, initial) => {
197
+ const map = new Map(initial);
198
+ HashSet.forEach(middleware, tag => {
199
+ map.set(tag.key, {
200
+ tag,
201
+ effect: Context.unsafeGet(context, tag)
202
+ });
203
+ });
204
+ return map;
205
+ };
206
+ const handlerToRoute = (endpoint_, middleware, handler, isFullResponse) => {
207
+ const endpoint = endpoint_;
208
+ const decodePath = Option.map(endpoint.pathSchema, Schema.decodeUnknown);
209
+ const isMultipart = endpoint.payloadSchema.pipe(Option.map(schema => HttpApiSchema.getMultipart(schema.ast)), Option.getOrElse(() => false));
210
+ const decodePayload = Option.map(endpoint.payloadSchema, Schema.decodeUnknown);
211
+ const decodeHeaders = Option.map(endpoint.headersSchema, Schema.decodeUnknown);
212
+ const decodeUrlParams = Option.map(endpoint.urlParamsSchema, Schema.decodeUnknown);
213
+ const encodeSuccess = Schema.encode(makeSuccessSchema(endpoint.successSchema));
214
+ return HttpRouter.makeRoute(endpoint.method, endpoint.path, applyMiddleware(middleware, Effect.withFiberRuntime(fiber => {
215
+ const context = fiber.getFiberRef(FiberRef.currentContext);
216
+ const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest);
217
+ const routeContext = Context.unsafeGet(context, HttpRouter.RouteContext);
218
+ const urlParams = Context.unsafeGet(context, HttpServerRequest.ParsedSearchParams);
219
+ 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 => {
220
+ const request = {
221
+ path: input.pathParams
222
+ };
223
+ if ("payload" in input) {
224
+ request.payload = input.payload;
225
+ }
226
+ if ("headers" in input) {
227
+ request.headers = input.headers;
228
+ }
229
+ if ("urlParams" in input) {
230
+ request.urlParams = input.urlParams;
158
231
  }
232
+ return handler(request);
233
+ }), isFullResponse ? identity : Effect.flatMap(encodeSuccess), Effect.catchIf(ParseResult.isParseError, HttpApiDecodeError.refailParseError));
234
+ })));
235
+ };
236
+ const applyMiddleware = (middleware, handler) => {
237
+ for (const entry of middleware.values()) {
238
+ const effect = HttpApiMiddleware.SecurityTypeId in entry.tag ? makeSecurityMiddleware(entry) : entry.effect;
239
+ if (entry.tag.optional) {
240
+ const previous = handler;
241
+ handler = Effect.matchEffect(effect, {
242
+ onFailure: () => previous,
243
+ onSuccess: entry.tag.provides !== undefined ? value => Effect.provideService(previous, entry.tag.provides, value) : _ => previous
244
+ });
159
245
  } else {
160
- routes.push(handlerToRoute(item.endpoint, function (request) {
161
- return Effect.mapInputContext(item.handler(request), input => Context.merge(context, input));
162
- }, item.withFullResponse));
246
+ handler = entry.tag.provides !== undefined ? Effect.provideServiceEffect(handler, entry.tag.provides, effect) : Effect.zipRight(effect, handler);
163
247
  }
164
248
  }
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}"`);
249
+ return handler;
250
+ };
251
+ const securityMiddlewareCache = /*#__PURE__*/globalValue("securityMiddlewareCache", () => new WeakMap());
252
+ const makeSecurityMiddleware = entry => {
253
+ if (securityMiddlewareCache.has(entry.tag)) {
254
+ return securityMiddlewareCache.get(entry.tag);
255
+ }
256
+ let effect;
257
+ for (const [key, security] of Object.entries(entry.tag.security)) {
258
+ const decode = securityDecode(security);
259
+ const handler = entry.effect[key];
260
+ const middleware = Effect.flatMap(decode, handler);
261
+ effect = effect === undefined ? middleware : Effect.catchAll(effect, () => middleware);
177
262
  }
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
- })
263
+ if (effect === undefined) {
264
+ effect = Effect.void;
265
+ }
266
+ securityMiddlewareCache.set(entry.tag, effect);
267
+ return effect;
268
+ };
269
+ const responseSchema = /*#__PURE__*/Schema.declare(HttpServerResponse.isServerResponse);
270
+ const makeSuccessSchema = schema => {
271
+ const schemas = new Set();
272
+ HttpApiSchema.deunionize(schemas, schema);
273
+ return Schema.Union(...Array.from(schemas, toResponseSuccess));
274
+ };
275
+ const makeErrorSchema = api => {
276
+ const schemas = new Set();
277
+ HttpApiSchema.deunionize(schemas, api.errorSchema);
278
+ HashMap.forEach(api.groups, group => {
279
+ HashMap.forEach(group.endpoints, endpoint => {
280
+ HttpApiSchema.deunionize(schemas, endpoint.errorSchema);
281
+ });
282
+ HttpApiSchema.deunionize(schemas, group.errorSchema);
187
283
  });
284
+ return Schema.Union(...Array.from(schemas, toResponseError));
188
285
  };
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
- });
286
+ const decodeForbidden = (_, __, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, _, "Encode only schema"));
287
+ const toResponseSchema = getStatus => {
288
+ const cache = new WeakMap();
289
+ const schemaToResponse = (data, _, ast) => {
290
+ const isEmpty = HttpApiSchema.isVoid(ast.to);
291
+ const status = getStatus(ast.to);
292
+ if (isEmpty) {
293
+ return HttpServerResponse.empty({
294
+ status
295
+ });
296
+ }
297
+ const encoding = HttpApiSchema.getEncoding(ast.to);
298
+ switch (encoding.kind) {
299
+ case "Json":
300
+ {
301
+ return Effect.mapError(HttpServerResponse.json(data, {
302
+ status,
303
+ contentType: encoding.contentType
304
+ }), error => new ParseResult.Type(ast, error, "Could not encode to JSON"));
305
+ }
306
+ case "Text":
307
+ {
308
+ return ParseResult.succeed(HttpServerResponse.text(data, {
309
+ status,
310
+ contentType: encoding.contentType
311
+ }));
312
+ }
313
+ case "Uint8Array":
314
+ {
315
+ return ParseResult.succeed(HttpServerResponse.uint8Array(data, {
316
+ status,
317
+ contentType: encoding.contentType
318
+ }));
319
+ }
320
+ case "UrlParams":
321
+ {
322
+ return ParseResult.succeed(HttpServerResponse.urlParams(data, {
323
+ status,
324
+ contentType: encoding.contentType
325
+ }));
326
+ }
327
+ }
328
+ };
329
+ return schema => {
330
+ if (cache.has(schema.ast)) {
331
+ return cache.get(schema.ast);
332
+ }
333
+ const transform = Schema.transformOrFail(responseSchema, schema, {
334
+ decode: decodeForbidden,
335
+ encode: schemaToResponse
336
+ });
337
+ cache.set(transform.ast, transform);
338
+ return transform;
339
+ };
340
+ };
341
+ const toResponseSuccess = /*#__PURE__*/toResponseSchema(HttpApiSchema.getStatusSuccessAST);
342
+ const toResponseError = /*#__PURE__*/toResponseSchema(HttpApiSchema.getStatusErrorAST);
343
+ // ----------------------------------------------------------------------------
344
+ // Global middleware
345
+ // ----------------------------------------------------------------------------
206
346
  /**
207
347
  * @since 1.0.0
208
348
  * @category middleware
@@ -222,7 +362,7 @@ const middlewareAddNoContext = middleware => Effect.map(Effect.serviceOption(Mid
222
362
  * @since 1.0.0
223
363
  * @category middleware
224
364
  */
225
- export const middlewareLayer = (...args) => {
365
+ export const middleware = (...args) => {
226
366
  const apiFirst = HttpApi.isHttpApi(args[0]);
227
367
  const withContext = apiFirst ? args[2]?.withContext === true : args[1]?.withContext === true;
228
368
  const add = withContext ? middlewareAdd : middlewareAddNoContext;
@@ -236,7 +376,7 @@ export const middlewareLayer = (...args) => {
236
376
  * @since 1.0.0
237
377
  * @category middleware
238
378
  */
239
- export const middlewareLayerScoped = (...args) => {
379
+ export const middlewareScoped = (...args) => {
240
380
  const apiFirst = HttpApi.isHttpApi(args[0]);
241
381
  const withContext = apiFirst ? args[2]?.withContext === true : args[1]?.withContext === true;
242
382
  const add = withContext ? middlewareAdd : middlewareAddNoContext;
@@ -249,7 +389,7 @@ export const middlewareLayerScoped = (...args) => {
249
389
  * @since 1.0.0
250
390
  * @category middleware
251
391
  */
252
- export const middlewareCors = options => middlewareLayer(HttpMiddleware.cors(options));
392
+ export const middlewareCors = options => middleware(HttpMiddleware.cors(options));
253
393
  /**
254
394
  * A middleware that adds an openapi.json endpoint to the API.
255
395
  *
@@ -257,7 +397,9 @@ export const middlewareCors = options => middlewareLayer(HttpMiddleware.cors(opt
257
397
  * @category middleware
258
398
  */
259
399
  export const middlewareOpenApi = options => Router.use(router => Effect.gen(function* () {
260
- const api = yield* HttpApi.HttpApi;
400
+ const {
401
+ api
402
+ } = yield* HttpApi.Api;
261
403
  const spec = OpenApi.fromApi(api);
262
404
  const response = yield* HttpServerResponse.json(spec).pipe(Effect.orDie);
263
405
  yield* router.get(options?.path ?? "/openapi.json", Effect.succeed(response));
@@ -265,7 +407,7 @@ export const middlewareOpenApi = options => Router.use(router => Effect.gen(func
265
407
  const bearerLen = `Bearer `.length;
266
408
  /**
267
409
  * @since 1.0.0
268
- * @category middleware
410
+ * @category security
269
411
  */
270
412
  export const securityDecode = self => {
271
413
  switch (self._tag) {
@@ -312,9 +454,9 @@ export const securityDecode = self => {
312
454
  * You can use this api before returning a response from an endpoint handler.
313
455
  *
314
456
  * ```ts
315
- * ApiBuilder.handle(
457
+ * handlers.handle(
316
458
  * "authenticate",
317
- * (_) => ApiBuilder.securitySetCookie(security, "secret123")
459
+ * (_) => HttpApiBuilder.securitySetCookie(security, "secret123")
318
460
  * )
319
461
  * ```
320
462
  *
@@ -329,152 +471,4 @@ export const securitySetCookie = (self, value, options) => {
329
471
  ...options
330
472
  })));
331
473
  };
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
474
  //# sourceMappingURL=HttpApiBuilder.js.map