@effect/platform 0.87.6 → 0.87.8

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.
@@ -14,7 +14,7 @@ import * as Scope from "effect/Scope"
14
14
  import * as Tracer from "effect/Tracer"
15
15
  import type * as Types from "effect/Types"
16
16
  import * as FindMyWay from "find-my-way-ts"
17
- import * as HttpApi from "./HttpApi.js"
17
+ import type * as HttpApi from "./HttpApi.js"
18
18
  import * as HttpApiBuilder from "./HttpApiBuilder.js"
19
19
  import type * as HttpApiGroup from "./HttpApiGroup.js"
20
20
  import type * as HttpMethod from "./HttpMethod.js"
@@ -73,6 +73,19 @@ export interface HttpRouter {
73
73
  | Type.From<"Error", Route.Error<Routes[number]>>
74
74
  >
75
75
 
76
+ readonly addGlobalMiddleware: <E, R>(
77
+ middleware:
78
+ & ((
79
+ effect: Effect.Effect<HttpServerResponse.HttpServerResponse, unhandled>
80
+ ) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>)
81
+ & (unhandled extends E ? unknown : "You cannot handle any errors")
82
+ ) => Effect.Effect<
83
+ void,
84
+ never,
85
+ | Type.From<"GlobalRequires", Exclude<R, GlobalProvided>>
86
+ | Type.From<"GlobalError", Exclude<E, unhandled>>
87
+ >
88
+
76
89
  readonly asHttpEffect: () => Effect.Effect<
77
90
  HttpServerResponse.HttpServerResponse,
78
91
  unknown,
@@ -94,6 +107,7 @@ export const HttpRouter: Context.Tag<HttpRouter, HttpRouter> = Context.GenericTa
94
107
  */
95
108
  export const make = Effect.gen(function*() {
96
109
  const router = FindMyWay.make<Route<any, never>>(yield* RouterConfig)
110
+ const middleware = new Set<middleware.Fn>()
97
111
 
98
112
  const addAll = <const Routes extends ReadonlyArray<Route<any, any>>>(
99
113
  routes: Routes
@@ -128,8 +142,7 @@ export const make = Effect.gen(function*() {
128
142
  [TypeId]: TypeId,
129
143
  prefixed(this: HttpRouter, prefix: string) {
130
144
  return HttpRouter.of({
131
- [TypeId]: TypeId,
132
- asHttpEffect: this.asHttpEffect,
145
+ ...this,
133
146
  prefixed: (newPrefix: string) => this.prefixed(prefixPath(prefix, newPrefix)),
134
147
  addAll: (routes) => addAll(routes.map(prefixRoute(prefix))) as any,
135
148
  add: (method, path, handler, options) =>
@@ -148,8 +161,12 @@ export const make = Effect.gen(function*() {
148
161
  },
149
162
  addAll,
150
163
  add: (method, path, handler, options) => addAll([route(method, path, handler, options)]),
164
+ addGlobalMiddleware: (middleware_) =>
165
+ Effect.sync(() => {
166
+ middleware.add(middleware_ as any)
167
+ }),
151
168
  asHttpEffect() {
152
- return Effect.withFiberRuntime((fiber) => {
169
+ let handler = Effect.withFiberRuntime<HttpServerResponse.HttpServerResponse, unknown>((fiber) => {
153
170
  const contextMap = new Map(fiber.currentContext.unsafeMap)
154
171
  const request = contextMap.get(HttpServerRequest.HttpServerRequest.key) as HttpServerRequest.HttpServerRequest
155
172
  let result = router.find(request.method, request.url)
@@ -185,6 +202,11 @@ export const make = Effect.gen(function*() {
185
202
  Context.unsafeMake(contextMap)
186
203
  )
187
204
  })
205
+ if (middleware.size === 0) return handler
206
+ for (const fn of Arr.reverse(middleware)) {
207
+ handler = fn(handler as any)
208
+ }
209
+ return handler
188
210
  }
189
211
  })
190
212
  })
@@ -246,7 +268,7 @@ export {
246
268
  * const MyRoute = Layer.scopedDiscard(Effect.gen(function*() {
247
269
  * const router = yield* HttpLayerRouter.HttpRouter
248
270
  *
249
- * // then use `router.add` to add a route
271
+ * // then use `yield* router.add(...)` to add a route
250
272
  * }))
251
273
  * ```
252
274
  *
@@ -257,6 +279,68 @@ export const use = <A, E, R>(
257
279
  f: (router: HttpRouter) => Effect.Effect<A, E, R>
258
280
  ): Layer.Layer<never, E, HttpRouter | Exclude<R, Scope.Scope>> => Layer.scopedDiscard(Effect.flatMap(HttpRouter, f))
259
281
 
282
+ /**
283
+ * Create a layer that adds a single route to the HTTP router.
284
+ *
285
+ * ```ts
286
+ * import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
287
+ * import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
288
+ *
289
+ * const Route = HttpLayerRouter.add("GET", "/hello", HttpServerResponse.text("Hello, World!"))
290
+ * ```
291
+ *
292
+ * @since 1.0.0
293
+ * @category HttpRouter
294
+ */
295
+ export const add = <E, R>(
296
+ method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS",
297
+ path: PathInput,
298
+ handler:
299
+ | Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
300
+ | ((request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>),
301
+ options?: {
302
+ readonly uninterruptible?: boolean | undefined
303
+ }
304
+ ): Layer.Layer<never, never, HttpRouter | Type.From<"Requires", Exclude<R, Provided>> | Type.From<"Error", E>> =>
305
+ use((router) => router.add(method, path, handler, options))
306
+
307
+ /**
308
+ * Create a layer that adds multiple routes to the HTTP router.
309
+ *
310
+ * ```ts
311
+ * import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
312
+ * import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
313
+ *
314
+ * const Routes = HttpLayerRouter.addAll([
315
+ * HttpLayerRouter.route("GET", "/hello", HttpServerResponse.text("Hello, World!"))
316
+ * ])
317
+ * ```
318
+ *
319
+ * @since 1.0.0
320
+ * @category HttpRouter
321
+ */
322
+ export const addAll = <Routes extends ReadonlyArray<Route<any, any>>, EX = never, RX = never>(
323
+ routes: Routes | Effect.Effect<Routes, EX, RX>,
324
+ options?: {
325
+ readonly prefix?: string | undefined
326
+ }
327
+ ): Layer.Layer<
328
+ never,
329
+ EX,
330
+ | HttpRouter
331
+ | Exclude<RX, Scope.Scope>
332
+ | Type.From<"Requires", Exclude<Route.Context<Routes[number]>, Provided>>
333
+ | Type.From<"Error", Route.Error<Routes[number]>>
334
+ > =>
335
+ Layer.scopedDiscard(Effect.gen(function*() {
336
+ const toAdd = Effect.isEffect(routes) ? yield* routes : routes
337
+ let router = yield* HttpRouter
338
+ if (options?.prefix) {
339
+ router = router.prefixed(options.prefix)
340
+ }
341
+ yield* router.addAll(toAdd)
342
+ }))
343
+
260
344
  /**
261
345
  * @since 1.0.0
262
346
  * @category HttpRouter
@@ -272,8 +356,8 @@ export const toHttpEffect = <A, E, R>(
272
356
  ): Effect.Effect<
273
357
  Effect.Effect<
274
358
  HttpServerResponse.HttpServerResponse,
275
- Type.Only<"Error", R> | HttpServerError.RouteNotFound,
276
- Scope.Scope | HttpServerRequest.HttpServerRequest | Type.Only<"Requires", R>
359
+ Type.Only<"Error", R> | Type.Only<"GlobalRequires", R> | HttpServerError.RouteNotFound,
360
+ Scope.Scope | HttpServerRequest.HttpServerRequest | Type.Only<"Requires", R> | Type.Only<"GlobalRequires", R>
277
361
  >,
278
362
  Type.Without<E>,
279
363
  Exclude<Type.Without<R>, HttpRouter> | Scope.Scope
@@ -474,6 +558,16 @@ export type Provided =
474
558
  | HttpServerRequest.ParsedSearchParams
475
559
  | RouteContext
476
560
 
561
+ /**
562
+ * Services provided to global middleware.
563
+ *
564
+ * @since 1.0.0
565
+ * @category Request types
566
+ */
567
+ export type GlobalProvided =
568
+ | HttpServerRequest.HttpServerRequest
569
+ | Scope.Scope
570
+
477
571
  /**
478
572
  * @since 1.0.0
479
573
  * @category Middleware
@@ -516,7 +610,9 @@ export interface Middleware<
516
610
  readonly layer: [Config["requires"]] extends [never] ? Layer.Layer<
517
611
  Type.From<"Requires", Config["provides"]>,
518
612
  Config["layerError"],
519
- Config["layerRequires"] | Type.From<"Requires", Config["requires"]>
613
+ | Config["layerRequires"]
614
+ | Type.From<"Requires", Config["requires"]>
615
+ | Type.From<"Error", Config["error"]>
520
616
  >
521
617
  : "Need to .combine(middleware) that satisfy the missing request dependencies"
522
618
 
@@ -542,6 +638,11 @@ export interface Middleware<
542
638
  /**
543
639
  * Create a middleware layer that can be used to modify requests and responses.
544
640
  *
641
+ * By default, the middleware only affects the routes that it is provided to.
642
+ *
643
+ * If you want to create a middleware that applies globally to all routes, pass
644
+ * the `global` option as `true`.
645
+ *
545
646
  * ```ts
546
647
  * import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
547
648
  * import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
@@ -607,15 +708,23 @@ export const middleware:
607
708
  if (arguments.length === 0) {
608
709
  return makeMiddleware as any
609
710
  }
610
- return makeMiddleware(arguments[0])
711
+ return makeMiddleware(arguments[0], arguments[1]) as any
611
712
  }
612
713
 
613
- const makeMiddleware = (middleware: any) =>
614
- new MiddlewareImpl(
615
- Effect.isEffect(middleware) ?
616
- Layer.scopedContext(Effect.map(middleware, (fn) => Context.unsafeMake(new Map([[fnContextKey, fn]])))) :
617
- Layer.succeedContext(Context.unsafeMake(new Map([[fnContextKey, middleware]]))) as any
618
- )
714
+ const makeMiddleware = (middleware: any, options?: {
715
+ readonly global?: boolean | undefined
716
+ }) =>
717
+ options?.global ?
718
+ Layer.scopedDiscard(Effect.gen(function*() {
719
+ const router = yield* HttpRouter
720
+ const fn = Effect.isEffect(middleware) ? yield* middleware : middleware
721
+ yield* router.addGlobalMiddleware(fn)
722
+ }))
723
+ : new MiddlewareImpl(
724
+ Effect.isEffect(middleware) ?
725
+ Layer.scopedContext(Effect.map(middleware, (fn) => Context.unsafeMake(new Map([[fnContextKey, fn]])))) :
726
+ Layer.succeedContext(Context.unsafeMake(new Map([[fnContextKey, middleware]]))) as any
727
+ )
619
728
 
620
729
  let middlewareId = 0
621
730
  const fnContextKey = "@effect/platform/HttpLayerRouter/MiddlewareFn"
@@ -711,7 +820,7 @@ export declare namespace middleware {
711
820
  * @category Middleware
712
821
  */
713
822
  export type Make<Provides = never, Handles = never> = {
714
- <E, R, EX, RX>(
823
+ <E, R, EX, RX, const Global extends boolean = false>(
715
824
  middleware: Effect.Effect<
716
825
  (
717
826
  effect: Effect.Effect<
@@ -728,16 +837,30 @@ export declare namespace middleware {
728
837
  & (unhandled extends E ? unknown : "You must only handle the configured errors"),
729
838
  EX,
730
839
  RX
731
- >
732
- ): Middleware<{
733
- provides: Provides
734
- handles: Handles
735
- error: Exclude<E, unhandled>
736
- requires: Exclude<R, Provided>
737
- layerError: EX
738
- layerRequires: Exclude<RX, Scope.Scope>
739
- }>
740
- <E, R>(
840
+ >,
841
+ options?: {
842
+ readonly global?: Global | undefined
843
+ }
844
+ ): Global extends true ? Layer.Layer<
845
+ | Type.From<"Requires", Provides>
846
+ | Type.From<"Error", Handles>
847
+ | Type.From<"GlobalRequires", Provides>
848
+ | Type.From<"GlobalError", Handles>,
849
+ EX,
850
+ | HttpRouter
851
+ | Exclude<RX, Scope.Scope>
852
+ | Type.From<"GlobalRequires", Exclude<R, GlobalProvided>>
853
+ | Type.From<"GlobalError", Exclude<E, unhandled>>
854
+ > :
855
+ Middleware<{
856
+ provides: Provides
857
+ handles: Handles
858
+ error: Exclude<E, unhandled>
859
+ requires: Exclude<R, Provided>
860
+ layerError: EX
861
+ layerRequires: Exclude<RX, Scope.Scope>
862
+ }>
863
+ <E, R, const Global extends boolean = false>(
741
864
  middleware:
742
865
  & ((
743
866
  effect: Effect.Effect<
@@ -750,15 +873,28 @@ export declare namespace middleware {
750
873
  E,
751
874
  R
752
875
  >)
753
- & (unhandled extends E ? unknown : "You must only handle the configured errors")
754
- ): Middleware<{
755
- provides: Provides
756
- handles: Handles
757
- error: Exclude<E, unhandled>
758
- requires: Exclude<R, Provided>
759
- layerError: never
760
- layerRequires: never
761
- }>
876
+ & (unhandled extends E ? unknown : "You must only handle the configured errors"),
877
+ options?: {
878
+ readonly global?: Global | undefined
879
+ }
880
+ ): Global extends true ? Layer.Layer<
881
+ | Type.From<"Requires", Provides>
882
+ | Type.From<"Error", Handles>
883
+ | Type.From<"GlobalRequires", Provides>
884
+ | Type.From<"GlobalError", Handles>,
885
+ never,
886
+ | HttpRouter
887
+ | Type.From<"GlobalRequires", Exclude<R, GlobalProvided>>
888
+ | Type.From<"GlobalError", Exclude<E, unhandled>>
889
+ > :
890
+ Middleware<{
891
+ provides: Provides
892
+ handles: Handles
893
+ error: Exclude<E, unhandled>
894
+ requires: Exclude<R, Provided>
895
+ layerError: never
896
+ layerRequires: never
897
+ }>
762
898
  }
763
899
 
764
900
  /**
@@ -785,7 +921,26 @@ export const cors = (
785
921
  readonly maxAge?: number | undefined
786
922
  readonly credentials?: boolean | undefined
787
923
  } | undefined
788
- ): Layer.Layer<never> => middleware(HttpMiddleware.cors(options)).layer
924
+ ): Layer.Layer<never, never, HttpRouter> => middleware(HttpMiddleware.cors(options), { global: true })
925
+
926
+ /**
927
+ * A middleware that disables the logger for some routes.
928
+ *
929
+ * ```ts
930
+ * import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
931
+ * import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
932
+ * import * as Layer from "effect/Layer"
933
+ *
934
+ * const Route = HttpLayerRouter.add("GET", "/hello", HttpServerResponse.text("Hello, World!")).pipe(
935
+ * // disable the logger for this route
936
+ * Layer.provide(HttpLayerRouter.disableLogger)
937
+ * )
938
+ * ```
939
+ *
940
+ * @since 1.0.0
941
+ * @category Middleware
942
+ */
943
+ export const disableLogger: Layer.Layer<never> = middleware(HttpMiddleware.withLoggerDisabled).layer
789
944
 
790
945
  /**
791
946
  * ```ts
@@ -853,20 +1008,14 @@ export const addHttpApi = <Id extends string, Groups extends HttpApiGroup.HttpAp
853
1008
  readonly openapiPath?: `/${string}` | undefined
854
1009
  }
855
1010
  ): Layer.Layer<
856
- HttpApi.Api,
1011
+ never,
857
1012
  never,
858
1013
  HttpRouter | HttpApiGroup.HttpApiGroup.ToService<Id, Groups> | R | HttpApiGroup.HttpApiGroup.ErrorContext<Groups>
859
1014
  > => {
860
- const ApiMiddleware = middleware(HttpApiBuilder.buildMiddleware(api)).layer
861
-
1015
+ const ApiMiddleware = middleware(HttpApiBuilder.buildMiddleware(api)).layer as Layer.Layer<never>
862
1016
  return HttpApiBuilder.Router.unwrap(Effect.fnUntraced(function*(router_) {
863
- const contextMap = new Map<string, unknown>()
864
1017
  const router = yield* HttpRouter
865
1018
  const routes = Arr.empty<Route<any, any>>()
866
- const context = yield* Effect.context<never>()
867
-
868
- contextMap.set(HttpApi.Api.key, { api, context })
869
-
870
1019
  for (const route of router_.routes) {
871
1020
  routes.push(makeRoute(route as any))
872
1021
  }
@@ -877,9 +1026,7 @@ export const addHttpApi = <Id extends string, Groups extends HttpApiGroup.HttpAp
877
1026
  const spec = OpenApi.fromApi(api)
878
1027
  yield* router.add("GET", options.openapiPath, Effect.succeed(HttpServerResponse.unsafeJson(spec)))
879
1028
  }
880
-
881
- return Context.unsafeMake<any>(contextMap)
882
- }, Layer.effectContext)).pipe(
1029
+ }, Layer.effectDiscard)).pipe(
883
1030
  Layer.provide(ApiMiddleware)
884
1031
  )
885
1032
  }
@@ -890,7 +1037,7 @@ export const addHttpApi = <Id extends string, Groups extends HttpApiGroup.HttpAp
890
1037
  * @since 1.0.0
891
1038
  * @category Server
892
1039
  */
893
- export const serve = <A, E, R, HE, HR = Type.Only<"Requires", R>>(
1040
+ export const serve = <A, E, R, HE, HR = Type.Only<"Requires", R> | Type.Only<"GlobalRequires", R>>(
894
1041
  appLayer: Layer.Layer<A, E, R>,
895
1042
  options?: {
896
1043
  readonly routerConfig?: Partial<FindMyWay.RouterConfig> | undefined
@@ -910,8 +1057,8 @@ export const serve = <A, E, R, HE, HR = Type.Only<"Requires", R>>(
910
1057
  readonly middleware?: (
911
1058
  effect: Effect.Effect<
912
1059
  HttpServerResponse.HttpServerResponse,
913
- Type.Only<"Error", R> | HttpServerError.RouteNotFound,
914
- Scope.Scope | HttpServerRequest.HttpServerRequest | Type.Only<"Requires", R>
1060
+ Type.Only<"Error", R> | Type.Only<"GlobalError", R> | HttpServerError.RouteNotFound,
1061
+ Scope.Scope | HttpServerRequest.HttpServerRequest | Type.Only<"Requires", R> | Type.Only<"GlobalRequires", R>
915
1062
  >
916
1063
  ) => Effect.Effect<HttpServerResponse.HttpServerResponse, HE, HR>
917
1064
  }
@@ -933,8 +1080,8 @@ export const serve = <A, E, R, HE, HR = Type.Only<"Requires", R>>(
933
1080
  return middleware ? HttpServer.serve(handler, middleware) : HttpServer.serve(handler)
934
1081
  }).pipe(
935
1082
  Layer.unwrapScoped,
936
- options?.disableListenLog ? identity : HttpServer.withLogAddress,
937
1083
  Layer.provide(appLayer),
938
- Layer.provide(RouterLayer)
1084
+ Layer.provide(RouterLayer),
1085
+ options?.disableListenLog ? identity : HttpServer.withLogAddress
939
1086
  ) as any
940
1087
  }
package/src/UrlParams.ts CHANGED
@@ -325,7 +325,7 @@ const baseUrl = (): string | undefined => {
325
325
  * @category conversions
326
326
  */
327
327
  export const toRecord = (self: UrlParams): Record<string, string | Arr.NonEmptyArray<string>> => {
328
- const out: Record<string, string | Arr.NonEmptyArray<string>> = {}
328
+ const out: Record<string, string | Arr.NonEmptyArray<string>> = Object.create(null)
329
329
  for (const [k, value] of self) {
330
330
  const curr = out[k]
331
331
  if (curr === undefined) {
@@ -336,7 +336,7 @@ export const toRecord = (self: UrlParams): Record<string, string | Arr.NonEmptyA
336
336
  curr.push(value)
337
337
  }
338
338
  }
339
- return out
339
+ return { ...out }
340
340
  }
341
341
 
342
342
  /**