@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.
Files changed (43) hide show
  1. package/HttpLayerRouter/package.json +6 -0
  2. package/README.md +272 -2
  3. package/dist/cjs/HttpApiBuilder.js +15 -1
  4. package/dist/cjs/HttpApiBuilder.js.map +1 -1
  5. package/dist/cjs/HttpApiScalar.js +29 -11
  6. package/dist/cjs/HttpApiScalar.js.map +1 -1
  7. package/dist/cjs/HttpApiSwagger.js +32 -15
  8. package/dist/cjs/HttpApiSwagger.js.map +1 -1
  9. package/dist/cjs/HttpLayerRouter.js +494 -0
  10. package/dist/cjs/HttpLayerRouter.js.map +1 -0
  11. package/dist/cjs/index.js +3 -1
  12. package/dist/cjs/internal/httpRouter.js +3 -1
  13. package/dist/cjs/internal/httpRouter.js.map +1 -1
  14. package/dist/dts/HttpApiBuilder.d.ts +6 -0
  15. package/dist/dts/HttpApiBuilder.d.ts.map +1 -1
  16. package/dist/dts/HttpApiScalar.d.ts +14 -2
  17. package/dist/dts/HttpApiScalar.d.ts.map +1 -1
  18. package/dist/dts/HttpApiSwagger.d.ts +12 -2
  19. package/dist/dts/HttpApiSwagger.d.ts.map +1 -1
  20. package/dist/dts/HttpLayerRouter.d.ts +481 -0
  21. package/dist/dts/HttpLayerRouter.d.ts.map +1 -0
  22. package/dist/dts/index.d.ts +4 -0
  23. package/dist/dts/index.d.ts.map +1 -1
  24. package/dist/dts/internal/httpRouter.d.ts.map +1 -1
  25. package/dist/esm/HttpApiBuilder.js +14 -0
  26. package/dist/esm/HttpApiBuilder.js.map +1 -1
  27. package/dist/esm/HttpApiScalar.js +28 -10
  28. package/dist/esm/HttpApiScalar.js.map +1 -1
  29. package/dist/esm/HttpApiSwagger.js +31 -14
  30. package/dist/esm/HttpApiSwagger.js.map +1 -1
  31. package/dist/esm/HttpLayerRouter.js +468 -0
  32. package/dist/esm/HttpLayerRouter.js.map +1 -0
  33. package/dist/esm/index.js +4 -0
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/internal/httpRouter.js +3 -1
  36. package/dist/esm/internal/httpRouter.js.map +1 -1
  37. package/package.json +10 -2
  38. package/src/HttpApiBuilder.ts +30 -0
  39. package/src/HttpApiScalar.ts +79 -41
  40. package/src/HttpApiSwagger.ts +49 -18
  41. package/src/HttpLayerRouter.ts +920 -0
  42. package/src/index.ts +5 -0
  43. 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
+ }