@effect/platform 0.87.0 → 0.87.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HttpLayerRouter/package.json +6 -0
- package/README.md +272 -2
- package/dist/cjs/HttpApiBuilder.js +15 -1
- package/dist/cjs/HttpApiBuilder.js.map +1 -1
- package/dist/cjs/HttpApiScalar.js +29 -11
- package/dist/cjs/HttpApiScalar.js.map +1 -1
- package/dist/cjs/HttpApiSwagger.js +32 -15
- package/dist/cjs/HttpApiSwagger.js.map +1 -1
- package/dist/cjs/HttpLayerRouter.js +494 -0
- package/dist/cjs/HttpLayerRouter.js.map +1 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/httpRouter.js +3 -1
- package/dist/cjs/internal/httpRouter.js.map +1 -1
- package/dist/dts/HttpApiBuilder.d.ts +6 -0
- package/dist/dts/HttpApiBuilder.d.ts.map +1 -1
- package/dist/dts/HttpApiScalar.d.ts +14 -2
- package/dist/dts/HttpApiScalar.d.ts.map +1 -1
- package/dist/dts/HttpApiSwagger.d.ts +12 -2
- package/dist/dts/HttpApiSwagger.d.ts.map +1 -1
- package/dist/dts/HttpLayerRouter.d.ts +481 -0
- package/dist/dts/HttpLayerRouter.d.ts.map +1 -0
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/httpRouter.d.ts.map +1 -1
- package/dist/esm/HttpApiBuilder.js +14 -0
- package/dist/esm/HttpApiBuilder.js.map +1 -1
- package/dist/esm/HttpApiScalar.js +28 -10
- package/dist/esm/HttpApiScalar.js.map +1 -1
- package/dist/esm/HttpApiSwagger.js +31 -14
- package/dist/esm/HttpApiSwagger.js.map +1 -1
- package/dist/esm/HttpLayerRouter.js +468 -0
- package/dist/esm/HttpLayerRouter.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/httpRouter.js +3 -1
- package/dist/esm/internal/httpRouter.js.map +1 -1
- package/package.json +10 -2
- package/src/HttpApiBuilder.ts +30 -0
- package/src/HttpApiScalar.ts +79 -41
- package/src/HttpApiSwagger.ts +49 -18
- package/src/HttpLayerRouter.ts +920 -0
- package/src/index.ts +5 -0
- package/src/internal/httpRouter.ts +4 -1
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
5
|
+
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
6
|
+
import * as Arr from "effect/Array"
|
|
7
|
+
import * as Context from "effect/Context"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as FiberRef from "effect/FiberRef"
|
|
10
|
+
import { compose, constant, dual, identity } from "effect/Function"
|
|
11
|
+
import * as Layer from "effect/Layer"
|
|
12
|
+
import * as Option from "effect/Option"
|
|
13
|
+
import * as Scope from "effect/Scope"
|
|
14
|
+
import * as Tracer from "effect/Tracer"
|
|
15
|
+
import * as FindMyWay from "find-my-way-ts"
|
|
16
|
+
import * as HttpApi from "./HttpApi.js"
|
|
17
|
+
import * as HttpApiBuilder from "./HttpApiBuilder.js"
|
|
18
|
+
import type * as HttpApiGroup from "./HttpApiGroup.js"
|
|
19
|
+
import type * as HttpMethod from "./HttpMethod.js"
|
|
20
|
+
import * as HttpMiddleware from "./HttpMiddleware.js"
|
|
21
|
+
import { RouteContext, RouteContextTypeId } from "./HttpRouter.js"
|
|
22
|
+
import * as HttpServer from "./HttpServer.js"
|
|
23
|
+
import * as HttpServerError from "./HttpServerError.js"
|
|
24
|
+
import * as OpenApi from "./OpenApi.js"
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @since 1.0.0
|
|
28
|
+
* @category Re-exports
|
|
29
|
+
*/
|
|
30
|
+
export * as FindMyWay from "find-my-way-ts"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @since 1.0.0
|
|
34
|
+
* @category HttpRouter
|
|
35
|
+
*/
|
|
36
|
+
export const TypeId: unique symbol = Symbol.for("@effect/platform/HttpLayerRouter/HttpRouter")
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @since 1.0.0
|
|
40
|
+
* @category HttpRouter
|
|
41
|
+
*/
|
|
42
|
+
export type TypeId = typeof TypeId
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @since 1.0.0
|
|
46
|
+
* @category HttpRouter
|
|
47
|
+
*/
|
|
48
|
+
export interface HttpRouter {
|
|
49
|
+
readonly [TypeId]: TypeId
|
|
50
|
+
|
|
51
|
+
readonly prefixed: (prefix: string) => HttpRouter
|
|
52
|
+
|
|
53
|
+
readonly add: <E, R>(
|
|
54
|
+
method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS",
|
|
55
|
+
path: PathInput,
|
|
56
|
+
handler:
|
|
57
|
+
| Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
|
|
58
|
+
| ((request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>),
|
|
59
|
+
options?: { readonly uninterruptible?: boolean | undefined } | undefined
|
|
60
|
+
) => Effect.Effect<
|
|
61
|
+
void,
|
|
62
|
+
never,
|
|
63
|
+
Type.From<"Requires", Exclude<R, Provided>> | Type.From<"Error", E>
|
|
64
|
+
>
|
|
65
|
+
|
|
66
|
+
readonly addAll: <const Routes extends ReadonlyArray<Route<any, any>>>(
|
|
67
|
+
routes: Routes
|
|
68
|
+
) => Effect.Effect<
|
|
69
|
+
void,
|
|
70
|
+
never,
|
|
71
|
+
| Type.From<"Requires", Exclude<Route.Context<Routes[number]>, Provided>>
|
|
72
|
+
| Type.From<"Error", Route.Error<Routes[number]>>
|
|
73
|
+
>
|
|
74
|
+
|
|
75
|
+
readonly asHttpEffect: () => Effect.Effect<
|
|
76
|
+
HttpServerResponse.HttpServerResponse,
|
|
77
|
+
unknown,
|
|
78
|
+
HttpServerRequest.HttpServerRequest | Scope.Scope
|
|
79
|
+
>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @since 1.0.0
|
|
84
|
+
* @category HttpRouter
|
|
85
|
+
*/
|
|
86
|
+
export const HttpRouter: Context.Tag<HttpRouter, HttpRouter> = Context.GenericTag<HttpRouter>(
|
|
87
|
+
"@effect/platform/HttpLayerRouter"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @since 1.0.0
|
|
92
|
+
* @category HttpRouter
|
|
93
|
+
*/
|
|
94
|
+
export const make = Effect.gen(function*() {
|
|
95
|
+
const router = FindMyWay.make<Route<any, never>>(yield* RouterConfig)
|
|
96
|
+
|
|
97
|
+
const addAll = <const Routes extends ReadonlyArray<Route<any, any>>>(
|
|
98
|
+
routes: Routes
|
|
99
|
+
): Effect.Effect<
|
|
100
|
+
void,
|
|
101
|
+
never,
|
|
102
|
+
| Type.From<"Requires", Exclude<Route.Context<Routes[number]>, Provided>>
|
|
103
|
+
| Type.From<"Error", Route.Error<Routes[number]>>
|
|
104
|
+
> =>
|
|
105
|
+
Effect.contextWith((context: Context.Context<never>) => {
|
|
106
|
+
const middleware = getMiddleware(context)
|
|
107
|
+
const applyMiddleware = (effect: Effect.Effect<HttpServerResponse.HttpServerResponse>) => {
|
|
108
|
+
for (let i = 0; i < middleware.length; i++) {
|
|
109
|
+
effect = middleware[i](effect)
|
|
110
|
+
}
|
|
111
|
+
return effect
|
|
112
|
+
}
|
|
113
|
+
for (let i = 0; i < routes.length; i++) {
|
|
114
|
+
const route = middleware.length === 0 ? routes[i] : makeRoute({
|
|
115
|
+
...routes[i],
|
|
116
|
+
handler: applyMiddleware(routes[i].handler as Effect.Effect<HttpServerResponse.HttpServerResponse>)
|
|
117
|
+
})
|
|
118
|
+
if (route.method === "*") {
|
|
119
|
+
router.all(route.path, route as any)
|
|
120
|
+
} else {
|
|
121
|
+
router.on(route.method, route.path, route as any)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
return HttpRouter.of({
|
|
127
|
+
[TypeId]: TypeId,
|
|
128
|
+
prefixed(this: HttpRouter, prefix: string) {
|
|
129
|
+
return HttpRouter.of({
|
|
130
|
+
[TypeId]: TypeId,
|
|
131
|
+
asHttpEffect: this.asHttpEffect,
|
|
132
|
+
prefixed: (newPrefix: string) => this.prefixed(prefixPath(prefix, newPrefix)),
|
|
133
|
+
addAll: (routes) => addAll(routes.map(prefixRoute(prefix))) as any,
|
|
134
|
+
add: (method, path, handler, options) =>
|
|
135
|
+
addAll([
|
|
136
|
+
makeRoute({
|
|
137
|
+
method,
|
|
138
|
+
path: prefixPath(path, prefix) as PathInput,
|
|
139
|
+
handler: Effect.isEffect(handler)
|
|
140
|
+
? handler
|
|
141
|
+
: Effect.flatMap(HttpServerRequest.HttpServerRequest, handler),
|
|
142
|
+
uninterruptible: options?.uninterruptible ?? false,
|
|
143
|
+
prefix: Option.some(prefix)
|
|
144
|
+
})
|
|
145
|
+
])
|
|
146
|
+
})
|
|
147
|
+
},
|
|
148
|
+
addAll,
|
|
149
|
+
add: (method, path, handler, options) => addAll([route(method, path, handler, options)]),
|
|
150
|
+
asHttpEffect() {
|
|
151
|
+
return Effect.withFiberRuntime((fiber) => {
|
|
152
|
+
const contextMap = new Map(fiber.currentContext.unsafeMap)
|
|
153
|
+
const request = contextMap.get(HttpServerRequest.HttpServerRequest.key) as HttpServerRequest.HttpServerRequest
|
|
154
|
+
let result = router.find(request.method, request.url)
|
|
155
|
+
if (result === undefined && request.method === "HEAD") {
|
|
156
|
+
result = router.find("GET", request.url)
|
|
157
|
+
}
|
|
158
|
+
if (result === undefined) {
|
|
159
|
+
return Effect.fail(new HttpServerError.RouteNotFound({ request }))
|
|
160
|
+
}
|
|
161
|
+
const route = result.handler
|
|
162
|
+
if (route.prefix._tag === "Some") {
|
|
163
|
+
contextMap.set(HttpServerRequest.HttpServerRequest.key, sliceRequestUrl(request, route.prefix.value))
|
|
164
|
+
}
|
|
165
|
+
contextMap.set(HttpServerRequest.ParsedSearchParams.key, result.searchParams)
|
|
166
|
+
contextMap.set(RouteContext.key, {
|
|
167
|
+
[RouteContextTypeId]: RouteContextTypeId,
|
|
168
|
+
route,
|
|
169
|
+
params: result.params
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const span = contextMap.get(Tracer.ParentSpan.key) as Tracer.Span | undefined
|
|
173
|
+
if (span && span._tag === "Span") {
|
|
174
|
+
span.attribute("http.route", route.path)
|
|
175
|
+
}
|
|
176
|
+
return Effect.locally(
|
|
177
|
+
(route.uninterruptible ?
|
|
178
|
+
route.handler :
|
|
179
|
+
Effect.interruptible(route.handler)) as Effect.Effect<
|
|
180
|
+
HttpServerResponse.HttpServerResponse,
|
|
181
|
+
unknown
|
|
182
|
+
>,
|
|
183
|
+
FiberRef.currentContext,
|
|
184
|
+
Context.unsafeMake(contextMap)
|
|
185
|
+
)
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
function sliceRequestUrl(request: HttpServerRequest.HttpServerRequest, prefix: string) {
|
|
192
|
+
const prefexLen = prefix.length
|
|
193
|
+
return request.modify({ url: request.url.length <= prefexLen ? "/" : request.url.slice(prefexLen) })
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @since 1.0.0
|
|
198
|
+
* @category Configuration
|
|
199
|
+
*/
|
|
200
|
+
export class RouterConfig extends Context.Reference<RouterConfig>()("@effect/platform/HttpLayerRouter/RouterConfig", {
|
|
201
|
+
defaultValue: constant<Partial<FindMyWay.RouterConfig>>({})
|
|
202
|
+
}) {}
|
|
203
|
+
|
|
204
|
+
export {
|
|
205
|
+
/**
|
|
206
|
+
* @since 1.0.0
|
|
207
|
+
* @category Route context
|
|
208
|
+
*/
|
|
209
|
+
params,
|
|
210
|
+
/**
|
|
211
|
+
* @since 1.0.0
|
|
212
|
+
* @category Route context
|
|
213
|
+
*/
|
|
214
|
+
RouteContext,
|
|
215
|
+
/**
|
|
216
|
+
* @since 1.0.0
|
|
217
|
+
* @category Route context
|
|
218
|
+
*/
|
|
219
|
+
schemaJson,
|
|
220
|
+
/**
|
|
221
|
+
* @since 1.0.0
|
|
222
|
+
* @category Route context
|
|
223
|
+
*/
|
|
224
|
+
schemaNoBody,
|
|
225
|
+
/**
|
|
226
|
+
* @since 1.0.0
|
|
227
|
+
* @category Route context
|
|
228
|
+
*/
|
|
229
|
+
schemaParams,
|
|
230
|
+
/**
|
|
231
|
+
* @since 1.0.0
|
|
232
|
+
* @category Route context
|
|
233
|
+
*/
|
|
234
|
+
schemaPathParams
|
|
235
|
+
} from "./HttpRouter.js"
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A helper function that is the equivalent of:
|
|
239
|
+
*
|
|
240
|
+
* ```ts
|
|
241
|
+
* import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
|
|
242
|
+
* import * as Effect from "effect/Effect"
|
|
243
|
+
* import * as Layer from "effect/Layer"
|
|
244
|
+
*
|
|
245
|
+
* const MyRoute = Layer.scopedDiscard(Effect.gen(function*() {
|
|
246
|
+
* const router = yield* HttpLayerRouter.HttpRouter
|
|
247
|
+
*
|
|
248
|
+
* // then use `router.add` to add a route
|
|
249
|
+
* }))
|
|
250
|
+
* ```
|
|
251
|
+
*
|
|
252
|
+
* @since 1.0.0
|
|
253
|
+
* @category HttpRouter
|
|
254
|
+
*/
|
|
255
|
+
export const use = <A, E, R>(
|
|
256
|
+
f: (router: HttpRouter) => Effect.Effect<A, E, R>
|
|
257
|
+
): Layer.Layer<never, E, HttpRouter | Exclude<R, Scope.Scope>> => Layer.scopedDiscard(Effect.flatMap(HttpRouter, f))
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @since 1.0.0
|
|
261
|
+
* @category HttpRouter
|
|
262
|
+
*/
|
|
263
|
+
export const layer: Layer.Layer<HttpRouter> = Layer.effect(HttpRouter, make)
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @since 1.0.0
|
|
267
|
+
* @category HttpRouter
|
|
268
|
+
*/
|
|
269
|
+
export const toHttpEffect = <A, E, R>(
|
|
270
|
+
appLayer: Layer.Layer<A, E, R>
|
|
271
|
+
): Effect.Effect<
|
|
272
|
+
Effect.Effect<
|
|
273
|
+
HttpServerResponse.HttpServerResponse,
|
|
274
|
+
Type.Only<"Error", R> | HttpServerError.RouteNotFound,
|
|
275
|
+
Scope.Scope | HttpServerRequest.HttpServerRequest | Type.Only<"Requires", R>
|
|
276
|
+
>,
|
|
277
|
+
E,
|
|
278
|
+
Exclude<Type.Without<R>, HttpRouter> | Scope.Scope
|
|
279
|
+
> =>
|
|
280
|
+
Effect.gen(function*() {
|
|
281
|
+
const scope = yield* Effect.scope
|
|
282
|
+
const memoMap = yield* Layer.CurrentMemoMap
|
|
283
|
+
const context = yield* Layer.buildWithMemoMap(
|
|
284
|
+
Layer.provideMerge(appLayer, layer),
|
|
285
|
+
memoMap,
|
|
286
|
+
scope
|
|
287
|
+
)
|
|
288
|
+
const router = Context.get(context, HttpRouter)
|
|
289
|
+
return router.asHttpEffect()
|
|
290
|
+
}) as any
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @since 1.0.0
|
|
294
|
+
* @category Route
|
|
295
|
+
*/
|
|
296
|
+
export const RouteTypeId: unique symbol = Symbol.for("@effect/platform/HttpLayerRouter/Route")
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @since 1.0.0
|
|
300
|
+
* @category Route
|
|
301
|
+
*/
|
|
302
|
+
export type RouteTypeId = typeof RouteTypeId
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @since 1.0.0
|
|
306
|
+
* @category Route
|
|
307
|
+
*/
|
|
308
|
+
export interface Route<E = never, R = never> {
|
|
309
|
+
readonly [RouteTypeId]: RouteTypeId
|
|
310
|
+
readonly method: HttpMethod.HttpMethod | "*"
|
|
311
|
+
readonly path: PathInput
|
|
312
|
+
readonly handler: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
|
|
313
|
+
readonly uninterruptible: boolean
|
|
314
|
+
readonly prefix: Option.Option<string>
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* @since 1.0.0
|
|
319
|
+
* @category Route
|
|
320
|
+
*/
|
|
321
|
+
export declare namespace Route {
|
|
322
|
+
/**
|
|
323
|
+
* @since 1.0.0
|
|
324
|
+
* @category Route
|
|
325
|
+
*/
|
|
326
|
+
export type Error<R extends Route<any, any>> = R extends Route<infer E, infer _R> ? E : never
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @since 1.0.0
|
|
330
|
+
* @category Route
|
|
331
|
+
*/
|
|
332
|
+
export type Context<T extends Route<any, any>> = T extends Route<infer _E, infer R> ? R : never
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const makeRoute = <E, R>(options: {
|
|
336
|
+
readonly method: HttpMethod.HttpMethod | "*"
|
|
337
|
+
readonly path: PathInput
|
|
338
|
+
readonly handler: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
|
|
339
|
+
readonly uninterruptible?: boolean | undefined
|
|
340
|
+
readonly prefix?: Option.Option<string> | undefined
|
|
341
|
+
}): Route<E, Exclude<R, Provided>> =>
|
|
342
|
+
(({
|
|
343
|
+
...options,
|
|
344
|
+
uninterruptible: options.uninterruptible ?? false,
|
|
345
|
+
prefix: options.prefix ?? Option.none(),
|
|
346
|
+
[RouteTypeId]: RouteTypeId
|
|
347
|
+
}) as Route<E, Exclude<R, Provided>>)
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @since 1.0.0
|
|
351
|
+
* @category Route
|
|
352
|
+
*/
|
|
353
|
+
export const route = <E, R>(
|
|
354
|
+
method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS",
|
|
355
|
+
path: PathInput,
|
|
356
|
+
handler:
|
|
357
|
+
| Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
|
|
358
|
+
| ((request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>),
|
|
359
|
+
options?: {
|
|
360
|
+
readonly uninterruptible?: boolean | undefined
|
|
361
|
+
}
|
|
362
|
+
): Route<E, Exclude<R, Provided>> =>
|
|
363
|
+
makeRoute({
|
|
364
|
+
...options,
|
|
365
|
+
method,
|
|
366
|
+
path,
|
|
367
|
+
handler: Effect.isEffect(handler) ? handler : Effect.flatMap(HttpServerRequest.HttpServerRequest, handler),
|
|
368
|
+
uninterruptible: options?.uninterruptible ?? false
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @since 1.0.0
|
|
373
|
+
* @category PathInput
|
|
374
|
+
*/
|
|
375
|
+
export type PathInput = `/${string}` | "*"
|
|
376
|
+
|
|
377
|
+
const removeTrailingSlash = (
|
|
378
|
+
path: PathInput
|
|
379
|
+
): PathInput => (path.endsWith("/") ? path.slice(0, -1) : path) as any
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* @since 1.0.0
|
|
383
|
+
* @category PathInput
|
|
384
|
+
*/
|
|
385
|
+
export const prefixPath: {
|
|
386
|
+
/**
|
|
387
|
+
* @since 1.0.0
|
|
388
|
+
* @category PathInput
|
|
389
|
+
*/
|
|
390
|
+
(prefix: string): (self: string) => string
|
|
391
|
+
/**
|
|
392
|
+
* @since 1.0.0
|
|
393
|
+
* @category PathInput
|
|
394
|
+
*/
|
|
395
|
+
(self: string, prefix: string): string
|
|
396
|
+
} = dual(2, (self: string, prefix: string) => {
|
|
397
|
+
prefix = removeTrailingSlash(prefix as PathInput)
|
|
398
|
+
return self === "/" ? prefix : prefix + self
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* @since 1.0.0
|
|
403
|
+
* @category Route
|
|
404
|
+
*/
|
|
405
|
+
export const prefixRoute: {
|
|
406
|
+
/**
|
|
407
|
+
* @since 1.0.0
|
|
408
|
+
* @category Route
|
|
409
|
+
*/
|
|
410
|
+
(prefix: string): <E, R>(self: Route<E, R>) => Route<E, R>
|
|
411
|
+
/**
|
|
412
|
+
* @since 1.0.0
|
|
413
|
+
* @category Route
|
|
414
|
+
*/
|
|
415
|
+
<E, R>(self: Route<E, R>, prefix: string): Route<E, R>
|
|
416
|
+
} = dual(2, <E, R>(self: Route<E, R>, prefix: string): Route<E, R> =>
|
|
417
|
+
makeRoute({
|
|
418
|
+
...self,
|
|
419
|
+
path: prefixPath(self.path, prefix) as PathInput,
|
|
420
|
+
prefix: Option.match(self.prefix, {
|
|
421
|
+
onNone: () => Option.some(prefix as string),
|
|
422
|
+
onSome: (existingPrefix) => Option.some(prefixPath(existingPrefix, prefix) as string)
|
|
423
|
+
})
|
|
424
|
+
}))
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Represents a request-level dependency, that needs to be provided by
|
|
428
|
+
* middleware.
|
|
429
|
+
*
|
|
430
|
+
* @since 1.0.0
|
|
431
|
+
* @category Request types
|
|
432
|
+
*/
|
|
433
|
+
export interface Type<Kind extends string, T> {
|
|
434
|
+
readonly _: unique symbol
|
|
435
|
+
readonly kind: Kind
|
|
436
|
+
readonly type: T
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* @since 1.0.0
|
|
441
|
+
* @category Request types
|
|
442
|
+
*/
|
|
443
|
+
export declare namespace Type {
|
|
444
|
+
/**
|
|
445
|
+
* @since 1.0.0
|
|
446
|
+
* @category Request types
|
|
447
|
+
*/
|
|
448
|
+
export type From<Kind extends string, R> = R extends infer T ? Type<Kind, T> : never
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* @since 1.0.0
|
|
452
|
+
* @category Request types
|
|
453
|
+
*/
|
|
454
|
+
export type Only<Kind extends string, A> = A extends Type<Kind, infer T> ? T : never
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* @since 1.0.0
|
|
458
|
+
* @category Request types
|
|
459
|
+
*/
|
|
460
|
+
export type Without<A> = A extends Type<infer _Kind, infer _> ? never : A
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Services provided by the HTTP router, which are available in the
|
|
465
|
+
* request context.
|
|
466
|
+
*
|
|
467
|
+
* @since 1.0.0
|
|
468
|
+
* @category Request types
|
|
469
|
+
*/
|
|
470
|
+
export type Provided =
|
|
471
|
+
| HttpServerRequest.HttpServerRequest
|
|
472
|
+
| Scope.Scope
|
|
473
|
+
| HttpServerRequest.ParsedSearchParams
|
|
474
|
+
| RouteContext
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* @since 1.0.0
|
|
478
|
+
* @category Middleware
|
|
479
|
+
*/
|
|
480
|
+
export const MiddlewareTypeId: unique symbol = Symbol.for("@effect/platform/HttpLayerRouter/Middleware")
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* @since 1.0.0
|
|
484
|
+
* @category Middleware
|
|
485
|
+
*/
|
|
486
|
+
export type MiddlewareTypeId = typeof MiddlewareTypeId
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* @since 1.0.0
|
|
490
|
+
* @category Middleware
|
|
491
|
+
*/
|
|
492
|
+
export interface Middleware<
|
|
493
|
+
Config extends {
|
|
494
|
+
provides: any
|
|
495
|
+
handles: any
|
|
496
|
+
error: any
|
|
497
|
+
requires: any
|
|
498
|
+
layerError: any
|
|
499
|
+
layerRequires: any
|
|
500
|
+
}
|
|
501
|
+
> {
|
|
502
|
+
readonly [MiddlewareTypeId]: Config
|
|
503
|
+
|
|
504
|
+
readonly layer: [Config["requires"]] extends [never] ? Layer.Layer<
|
|
505
|
+
Type.From<"Requires", Config["provides"]>,
|
|
506
|
+
Config["layerError"],
|
|
507
|
+
Config["layerRequires"] | Type.From<"Requires", Config["requires"]>
|
|
508
|
+
>
|
|
509
|
+
: "Need to .provide(middleware) that satisfy the missing request dependencies"
|
|
510
|
+
|
|
511
|
+
readonly combine: <
|
|
512
|
+
Config2 extends {
|
|
513
|
+
provides: any
|
|
514
|
+
handles: any
|
|
515
|
+
error: any
|
|
516
|
+
requires: any
|
|
517
|
+
layerError: any
|
|
518
|
+
layerRequires: any
|
|
519
|
+
}
|
|
520
|
+
>(other: Middleware<Config2>) => Middleware<{
|
|
521
|
+
provides: Config2["provides"] | Config["provides"]
|
|
522
|
+
handles: Config2["handles"] | Config["handles"]
|
|
523
|
+
error: Config2["error"] | Exclude<Config["error"], Config2["handles"]>
|
|
524
|
+
requires: Exclude<Config["requires"], Config2["provides"]> | Config2["requires"]
|
|
525
|
+
layerError: Config["layerError"] | Config2["layerError"]
|
|
526
|
+
layerRequires: Config["layerRequires"] | Config2["layerRequires"]
|
|
527
|
+
}>
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Create a middleware layer that can be used to modify requests and responses.
|
|
532
|
+
*
|
|
533
|
+
* ```ts
|
|
534
|
+
* import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
|
|
535
|
+
* import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
|
|
536
|
+
* import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
537
|
+
* import * as Context from "effect/Context"
|
|
538
|
+
* import * as Effect from "effect/Effect"
|
|
539
|
+
* import * as Layer from "effect/Layer"
|
|
540
|
+
*
|
|
541
|
+
* // Here we are defining a CORS middleware
|
|
542
|
+
* const CorsMiddleware = HttpLayerRouter.middleware(HttpMiddleware.cors()).layer
|
|
543
|
+
* // You can also use HttpLayerRouter.cors() to create a CORS middleware
|
|
544
|
+
*
|
|
545
|
+
* class CurrentSession extends Context.Tag("CurrentSession")<CurrentSession, {
|
|
546
|
+
* readonly token: string
|
|
547
|
+
* }>() {}
|
|
548
|
+
*
|
|
549
|
+
* // You can create middleware that provides a service to the HTTP requests.
|
|
550
|
+
* const SessionMiddleware = HttpLayerRouter.middleware<{
|
|
551
|
+
* provides: CurrentSession
|
|
552
|
+
* }>()(
|
|
553
|
+
* Effect.gen(function*() {
|
|
554
|
+
* yield* Effect.log("SessionMiddleware initialized")
|
|
555
|
+
*
|
|
556
|
+
* return (httpEffect) =>
|
|
557
|
+
* Effect.provideService(httpEffect, CurrentSession, {
|
|
558
|
+
* token: "dummy-token"
|
|
559
|
+
* })
|
|
560
|
+
* })
|
|
561
|
+
* ).layer
|
|
562
|
+
*
|
|
563
|
+
* Effect.gen(function*() {
|
|
564
|
+
* const router = yield* HttpLayerRouter.HttpRouter
|
|
565
|
+
* yield* router.add(
|
|
566
|
+
* "GET",
|
|
567
|
+
* "/hello",
|
|
568
|
+
* Effect.gen(function*() {
|
|
569
|
+
* // Requests can now access the current session
|
|
570
|
+
* const session = yield* CurrentSession
|
|
571
|
+
* return HttpServerResponse.text(`Hello, World! Your token is ${session.token}`)
|
|
572
|
+
* })
|
|
573
|
+
* )
|
|
574
|
+
* }).pipe(
|
|
575
|
+
* Layer.effectDiscard,
|
|
576
|
+
* // Provide the SessionMiddleware & CorsMiddleware to some routes
|
|
577
|
+
* Layer.provide([SessionMiddleware, CorsMiddleware])
|
|
578
|
+
* )
|
|
579
|
+
* ```
|
|
580
|
+
*
|
|
581
|
+
* @since 1.0.0
|
|
582
|
+
* @category Middleware
|
|
583
|
+
*/
|
|
584
|
+
export const middleware:
|
|
585
|
+
& middleware.Make<never, never>
|
|
586
|
+
& (<
|
|
587
|
+
Config extends {
|
|
588
|
+
provides?: any
|
|
589
|
+
handles?: any
|
|
590
|
+
} = {}
|
|
591
|
+
>() => middleware.Make<
|
|
592
|
+
Config extends { provides: infer R } ? R : never,
|
|
593
|
+
Config extends { handles: infer E } ? E : never
|
|
594
|
+
>) = function() {
|
|
595
|
+
if (arguments.length === 0) {
|
|
596
|
+
return makeMiddleware as any
|
|
597
|
+
}
|
|
598
|
+
return makeMiddleware(arguments[0])
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const makeMiddleware = (middleware: any) =>
|
|
602
|
+
new MiddlewareImpl(
|
|
603
|
+
Effect.isEffect(middleware) ?
|
|
604
|
+
Layer.scopedContext(Effect.map(middleware, (fn) => Context.unsafeMake(new Map([[fnContextKey, fn]])))) :
|
|
605
|
+
Layer.succeedContext(Context.unsafeMake(new Map([[fnContextKey, middleware]]))) as any
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
let middlewareId = 0
|
|
609
|
+
const fnContextKey = "@effect/platform/HttpLayerRouter/MiddlewareFn"
|
|
610
|
+
|
|
611
|
+
class MiddlewareImpl<
|
|
612
|
+
Config extends {
|
|
613
|
+
provides: any
|
|
614
|
+
handles: any
|
|
615
|
+
error: any
|
|
616
|
+
requires: any
|
|
617
|
+
layerError: any
|
|
618
|
+
layerRequires: any
|
|
619
|
+
}
|
|
620
|
+
> implements Middleware<Config> {
|
|
621
|
+
readonly [MiddlewareTypeId]: Config = {} as any
|
|
622
|
+
|
|
623
|
+
constructor(
|
|
624
|
+
readonly layerFn: Layer.Layer<never>,
|
|
625
|
+
readonly dependencies?: Layer.Layer<any, any, any>
|
|
626
|
+
) {
|
|
627
|
+
const contextKey = `@effect/platform/HttpLayerRouter/Middleware-${++middlewareId}` as const
|
|
628
|
+
this.layer = Layer.scopedContext(Effect.gen(this, function*() {
|
|
629
|
+
const context = yield* Effect.context<Scope.Scope>()
|
|
630
|
+
const stack = [context.unsafeMap.get(fnContextKey)]
|
|
631
|
+
if (this.dependencies) {
|
|
632
|
+
const memoMap = yield* Layer.CurrentMemoMap
|
|
633
|
+
const scope = Context.get(context, Scope.Scope)
|
|
634
|
+
const depsContext = yield* Layer.buildWithMemoMap(this.dependencies, memoMap, scope)
|
|
635
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
636
|
+
stack.push(...getMiddleware(depsContext))
|
|
637
|
+
}
|
|
638
|
+
return Context.unsafeMake<never>(new Map([[contextKey, stack]]))
|
|
639
|
+
})).pipe(Layer.provide(this.layerFn))
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
layer: any
|
|
643
|
+
|
|
644
|
+
combine<
|
|
645
|
+
Config2 extends {
|
|
646
|
+
provides: any
|
|
647
|
+
handles: any
|
|
648
|
+
error: any
|
|
649
|
+
requires: any
|
|
650
|
+
layerError: any
|
|
651
|
+
layerRequires: any
|
|
652
|
+
}
|
|
653
|
+
>(other: Middleware<Config2>): Middleware<any> {
|
|
654
|
+
return new MiddlewareImpl(
|
|
655
|
+
this.layerFn,
|
|
656
|
+
this.dependencies ? Layer.provideMerge(this.dependencies, other.layer as any) : other.layer as any
|
|
657
|
+
) as any
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const middlewareCache = new WeakMap<Context.Context<never>, any>()
|
|
662
|
+
const getMiddleware = (context: Context.Context<never>): Array<middleware.Fn> => {
|
|
663
|
+
let arr = middlewareCache.get(context)
|
|
664
|
+
if (arr) return arr
|
|
665
|
+
const topLevel = Arr.empty<Array<middleware.Fn>>()
|
|
666
|
+
let maxLength = 0
|
|
667
|
+
for (const [key, value] of context.unsafeMap) {
|
|
668
|
+
if (key.startsWith("@effect/platform/HttpLayerRouter/Middleware-")) {
|
|
669
|
+
topLevel.push(value)
|
|
670
|
+
if (value.length > maxLength) {
|
|
671
|
+
maxLength = value.length
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (topLevel.length === 0) {
|
|
676
|
+
arr = []
|
|
677
|
+
} else {
|
|
678
|
+
const middleware = new Set<middleware.Fn>()
|
|
679
|
+
for (let i = maxLength - 1; i >= 0; i--) {
|
|
680
|
+
for (const arr of topLevel) {
|
|
681
|
+
if (i < arr.length) {
|
|
682
|
+
middleware.add(arr[i])
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
arr = Arr.fromIterable(middleware).reverse()
|
|
687
|
+
}
|
|
688
|
+
middlewareCache.set(context, arr)
|
|
689
|
+
return arr
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* @since 1.0.0
|
|
694
|
+
* @category Middleware
|
|
695
|
+
*/
|
|
696
|
+
export declare namespace middleware {
|
|
697
|
+
/**
|
|
698
|
+
* @since 1.0.0
|
|
699
|
+
* @category Middleware
|
|
700
|
+
*/
|
|
701
|
+
export type Make<Provides = never, Handles = never> = {
|
|
702
|
+
<E, R, EX, RX>(
|
|
703
|
+
middleware: Effect.Effect<
|
|
704
|
+
(
|
|
705
|
+
effect: Effect.Effect<
|
|
706
|
+
HttpServerResponse.HttpServerResponse,
|
|
707
|
+
Handles,
|
|
708
|
+
Provides
|
|
709
|
+
>
|
|
710
|
+
) => Effect.Effect<
|
|
711
|
+
HttpServerResponse.HttpServerResponse,
|
|
712
|
+
E,
|
|
713
|
+
R
|
|
714
|
+
>,
|
|
715
|
+
EX,
|
|
716
|
+
RX
|
|
717
|
+
>
|
|
718
|
+
): Middleware<{
|
|
719
|
+
provides: Provides
|
|
720
|
+
handles: Handles
|
|
721
|
+
error: E
|
|
722
|
+
requires: Exclude<R, Provided>
|
|
723
|
+
layerError: EX
|
|
724
|
+
layerRequires: Exclude<RX, Scope.Scope>
|
|
725
|
+
}>
|
|
726
|
+
<E, R>(
|
|
727
|
+
middleware: (
|
|
728
|
+
effect: Effect.Effect<
|
|
729
|
+
HttpServerResponse.HttpServerResponse,
|
|
730
|
+
Handles,
|
|
731
|
+
Provides
|
|
732
|
+
>
|
|
733
|
+
) => Effect.Effect<
|
|
734
|
+
HttpServerResponse.HttpServerResponse,
|
|
735
|
+
E,
|
|
736
|
+
R
|
|
737
|
+
>
|
|
738
|
+
): Middleware<{
|
|
739
|
+
provides: Provides
|
|
740
|
+
handles: Handles
|
|
741
|
+
error: E
|
|
742
|
+
requires: Exclude<R, Provided>
|
|
743
|
+
layerError: never
|
|
744
|
+
layerRequires: never
|
|
745
|
+
}>
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* @since 1.0.0
|
|
750
|
+
* @category Middleware
|
|
751
|
+
*/
|
|
752
|
+
export type Fn = (
|
|
753
|
+
effect: Effect.Effect<HttpServerResponse.HttpServerResponse>
|
|
754
|
+
) => Effect.Effect<HttpServerResponse.HttpServerResponse>
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* A middleware that applies CORS headers to the HTTP response.
|
|
759
|
+
*
|
|
760
|
+
* @since 1.0.0
|
|
761
|
+
* @category Middleware
|
|
762
|
+
*/
|
|
763
|
+
export const cors = (
|
|
764
|
+
options?: {
|
|
765
|
+
readonly allowedOrigins?: ReadonlyArray<string> | undefined
|
|
766
|
+
readonly allowedMethods?: ReadonlyArray<string> | undefined
|
|
767
|
+
readonly allowedHeaders?: ReadonlyArray<string> | undefined
|
|
768
|
+
readonly exposedHeaders?: ReadonlyArray<string> | undefined
|
|
769
|
+
readonly maxAge?: number | undefined
|
|
770
|
+
readonly credentials?: boolean | undefined
|
|
771
|
+
} | undefined
|
|
772
|
+
): Layer.Layer<never> => middleware(HttpMiddleware.cors(options)).layer
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* ```ts
|
|
776
|
+
* import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"
|
|
777
|
+
* import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
|
|
778
|
+
* import * as HttpApi from "@effect/platform/HttpApi"
|
|
779
|
+
* import * as HttpApiBuilder from "@effect/platform/HttpApiBuilder"
|
|
780
|
+
* import * as HttpApiEndpoint from "@effect/platform/HttpApiEndpoint"
|
|
781
|
+
* import * as HttpApiGroup from "@effect/platform/HttpApiGroup"
|
|
782
|
+
* import * as HttpApiScalar from "@effect/platform/HttpApiScalar"
|
|
783
|
+
* import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
|
|
784
|
+
* import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
|
|
785
|
+
* import * as Effect from "effect/Effect"
|
|
786
|
+
* import * as Layer from "effect/Layer"
|
|
787
|
+
* import { createServer } from "http"
|
|
788
|
+
*
|
|
789
|
+
* // First, we define our HttpApi
|
|
790
|
+
* class MyApi extends HttpApi.make("api").add(
|
|
791
|
+
* HttpApiGroup.make("users").add(
|
|
792
|
+
* HttpApiEndpoint.get("me", "/me")
|
|
793
|
+
* ).prefix("/users")
|
|
794
|
+
* ) {}
|
|
795
|
+
*
|
|
796
|
+
* // Implement the handlers for the API
|
|
797
|
+
* const UsersApiLayer = HttpApiBuilder.group(MyApi, "users", (handers) => handers.handle("me", () => Effect.void))
|
|
798
|
+
*
|
|
799
|
+
* // Use `HttpLayerRouter.addHttpApi` to register the API with the router
|
|
800
|
+
* const HttpApiRoutes = HttpLayerRouter.addHttpApi(MyApi, {
|
|
801
|
+
* openapiPath: "/docs/openapi.json"
|
|
802
|
+
* }).pipe(
|
|
803
|
+
* // Provide the api handlers layer
|
|
804
|
+
* Layer.provide(UsersApiLayer)
|
|
805
|
+
* )
|
|
806
|
+
*
|
|
807
|
+
* // Create a /docs route for the API documentation
|
|
808
|
+
* const DocsRoute = HttpApiScalar.layerHttpLayerRouter({
|
|
809
|
+
* api: MyApi,
|
|
810
|
+
* path: "/docs"
|
|
811
|
+
* })
|
|
812
|
+
*
|
|
813
|
+
* const CorsMiddleware = HttpLayerRouter.middleware(HttpMiddleware.cors())
|
|
814
|
+
* // You can also use HttpLayerRouter.cors() to create a CORS middleware
|
|
815
|
+
*
|
|
816
|
+
* // Finally, we merge all routes and serve them using the Node HTTP server
|
|
817
|
+
* const AllRoutes = Layer.mergeAll(
|
|
818
|
+
* HttpApiRoutes,
|
|
819
|
+
* DocsRoute
|
|
820
|
+
* ).pipe(
|
|
821
|
+
* Layer.provide(CorsMiddleware.layer)
|
|
822
|
+
* )
|
|
823
|
+
*
|
|
824
|
+
* HttpLayerRouter.serve(AllRoutes).pipe(
|
|
825
|
+
* Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })),
|
|
826
|
+
* Layer.launch,
|
|
827
|
+
* NodeRuntime.runMain
|
|
828
|
+
* )
|
|
829
|
+
* ```
|
|
830
|
+
*
|
|
831
|
+
* @since 1.0.0
|
|
832
|
+
* @category HttpApi
|
|
833
|
+
*/
|
|
834
|
+
export const addHttpApi = <Id extends string, Groups extends HttpApiGroup.HttpApiGroup.Any, E, R>(
|
|
835
|
+
api: HttpApi.HttpApi<Id, Groups, E, R>,
|
|
836
|
+
options?: {
|
|
837
|
+
readonly openapiPath?: `/${string}` | undefined
|
|
838
|
+
}
|
|
839
|
+
): Layer.Layer<
|
|
840
|
+
HttpApi.Api,
|
|
841
|
+
never,
|
|
842
|
+
HttpRouter | HttpApiGroup.HttpApiGroup.ToService<Id, Groups> | R | HttpApiGroup.HttpApiGroup.ErrorContext<Groups>
|
|
843
|
+
> => {
|
|
844
|
+
const ApiMiddleware = middleware(HttpApiBuilder.buildMiddleware(api)).layer
|
|
845
|
+
|
|
846
|
+
return HttpApiBuilder.Router.unwrap(Effect.fnUntraced(function*(router_) {
|
|
847
|
+
const contextMap = new Map<string, unknown>()
|
|
848
|
+
const router = yield* HttpRouter
|
|
849
|
+
const routes = Arr.empty<Route<any, any>>()
|
|
850
|
+
const context = yield* Effect.context<never>()
|
|
851
|
+
|
|
852
|
+
contextMap.set(HttpApi.Api.key, { api, context })
|
|
853
|
+
|
|
854
|
+
for (const route of router_.routes) {
|
|
855
|
+
routes.push(makeRoute(route as any))
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
yield* (router.addAll(routes) as Effect.Effect<void>)
|
|
859
|
+
|
|
860
|
+
if (options?.openapiPath) {
|
|
861
|
+
const spec = OpenApi.fromApi(api)
|
|
862
|
+
yield* router.add("GET", options.openapiPath, Effect.succeed(HttpServerResponse.unsafeJson(spec)))
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return Context.unsafeMake<any>(contextMap)
|
|
866
|
+
}, Layer.effectContext)).pipe(
|
|
867
|
+
Layer.provide(ApiMiddleware)
|
|
868
|
+
)
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Serves the provided application layer as an HTTP server.
|
|
873
|
+
*
|
|
874
|
+
* @since 1.0.0
|
|
875
|
+
* @category Server
|
|
876
|
+
*/
|
|
877
|
+
export const serve = <A, E, R, HE, HR = Type.Only<"Requires", R>>(
|
|
878
|
+
appLayer: Layer.Layer<A, E, R>,
|
|
879
|
+
options?: {
|
|
880
|
+
readonly routerConfig?: Partial<FindMyWay.RouterConfig> | undefined
|
|
881
|
+
readonly disableLogger?: boolean | undefined
|
|
882
|
+
readonly disableListenLog?: boolean
|
|
883
|
+
/**
|
|
884
|
+
* Middleware to apply to the HTTP server.
|
|
885
|
+
*
|
|
886
|
+
* NOTE: This middleware is applied to the entire HTTP server chain,
|
|
887
|
+
* including the sending of the response. This means that modifications
|
|
888
|
+
* to the response **WILL NOT** be reflected in the final response sent to the
|
|
889
|
+
* client.
|
|
890
|
+
*
|
|
891
|
+
* Use HttpLayerRouter.middleware to create middleware that can modify the
|
|
892
|
+
* response.
|
|
893
|
+
*/
|
|
894
|
+
readonly middleware?: (
|
|
895
|
+
effect: Effect.Effect<
|
|
896
|
+
HttpServerResponse.HttpServerResponse,
|
|
897
|
+
Type.Only<"Error", R> | HttpServerError.RouteNotFound,
|
|
898
|
+
Scope.Scope | HttpServerRequest.HttpServerRequest | Type.Only<"Requires", R>
|
|
899
|
+
>
|
|
900
|
+
) => Effect.Effect<HttpServerResponse.HttpServerResponse, HE, HR>
|
|
901
|
+
}
|
|
902
|
+
): Layer.Layer<never, E, HttpServer.HttpServer | Exclude<Type.Without<R> | Exclude<HR, Provided>, HttpRouter>> => {
|
|
903
|
+
let middleware: any = options?.middleware
|
|
904
|
+
if (options?.disableLogger !== true) {
|
|
905
|
+
middleware = middleware ? compose(middleware, HttpMiddleware.logger) : HttpMiddleware.logger
|
|
906
|
+
}
|
|
907
|
+
const RouterLayer = options?.routerConfig
|
|
908
|
+
? Layer.provide(layer, Layer.succeed(RouterConfig, options.routerConfig))
|
|
909
|
+
: layer
|
|
910
|
+
return Effect.gen(function*() {
|
|
911
|
+
const router = yield* HttpRouter
|
|
912
|
+
const handler = router.asHttpEffect()
|
|
913
|
+
return middleware ? HttpServer.serve(handler, middleware) : HttpServer.serve(handler)
|
|
914
|
+
}).pipe(
|
|
915
|
+
Layer.unwrapScoped,
|
|
916
|
+
options?.disableListenLog ? identity : HttpServer.withLogAddress,
|
|
917
|
+
Layer.provide(appLayer),
|
|
918
|
+
Layer.provide(RouterLayer)
|
|
919
|
+
) as any
|
|
920
|
+
}
|