@effect/platform 0.48.27 → 0.48.28

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 (56) hide show
  1. package/README.md +51 -0
  2. package/dist/cjs/Http/App.js +50 -32
  3. package/dist/cjs/Http/App.js.map +1 -1
  4. package/dist/cjs/Http/Headers.js +4 -1
  5. package/dist/cjs/Http/Headers.js.map +1 -1
  6. package/dist/cjs/Http/Router.js +6 -1
  7. package/dist/cjs/Http/Router.js.map +1 -1
  8. package/dist/cjs/Http/UrlParams.js +1 -1
  9. package/dist/cjs/Http/UrlParams.js.map +1 -1
  10. package/dist/cjs/Transferable.js +6 -3
  11. package/dist/cjs/Transferable.js.map +1 -1
  12. package/dist/cjs/WorkerError.js +9 -6
  13. package/dist/cjs/WorkerError.js.map +1 -1
  14. package/dist/cjs/internal/http/middleware.js +14 -16
  15. package/dist/cjs/internal/http/middleware.js.map +1 -1
  16. package/dist/cjs/internal/http/multipart.js +2 -2
  17. package/dist/cjs/internal/http/multipart.js.map +1 -1
  18. package/dist/cjs/internal/http/router.js +17 -6
  19. package/dist/cjs/internal/http/router.js.map +1 -1
  20. package/dist/dts/Http/App.d.ts +5 -7
  21. package/dist/dts/Http/App.d.ts.map +1 -1
  22. package/dist/dts/Http/Headers.d.ts.map +1 -1
  23. package/dist/dts/Http/Router.d.ts +5 -0
  24. package/dist/dts/Http/Router.d.ts.map +1 -1
  25. package/dist/dts/Transferable.d.ts.map +1 -1
  26. package/dist/dts/WorkerError.d.ts +2 -2
  27. package/dist/dts/WorkerError.d.ts.map +1 -1
  28. package/dist/dts/internal/http/router.d.ts.map +1 -1
  29. package/dist/esm/Http/App.js +48 -30
  30. package/dist/esm/Http/App.js.map +1 -1
  31. package/dist/esm/Http/Headers.js +4 -1
  32. package/dist/esm/Http/Headers.js.map +1 -1
  33. package/dist/esm/Http/Router.js +5 -0
  34. package/dist/esm/Http/Router.js.map +1 -1
  35. package/dist/esm/Http/UrlParams.js +1 -1
  36. package/dist/esm/Http/UrlParams.js.map +1 -1
  37. package/dist/esm/Transferable.js +6 -3
  38. package/dist/esm/Transferable.js.map +1 -1
  39. package/dist/esm/WorkerError.js +9 -6
  40. package/dist/esm/WorkerError.js.map +1 -1
  41. package/dist/esm/internal/http/middleware.js +14 -16
  42. package/dist/esm/internal/http/middleware.js.map +1 -1
  43. package/dist/esm/internal/http/multipart.js +2 -2
  44. package/dist/esm/internal/http/multipart.js.map +1 -1
  45. package/dist/esm/internal/http/router.js +15 -5
  46. package/dist/esm/internal/http/router.js.map +1 -1
  47. package/package.json +3 -3
  48. package/src/Http/App.ts +71 -46
  49. package/src/Http/Headers.ts +2 -3
  50. package/src/Http/Router.ts +7 -0
  51. package/src/Http/UrlParams.ts +2 -2
  52. package/src/Transferable.ts +3 -4
  53. package/src/WorkerError.ts +11 -9
  54. package/src/internal/http/middleware.ts +7 -7
  55. package/src/internal/http/multipart.ts +2 -2
  56. package/src/internal/http/router.ts +45 -39
package/src/Http/App.ts CHANGED
@@ -2,16 +2,18 @@
2
2
  * @since 1.0.0
3
3
  */
4
4
  import * as Cause from "effect/Cause"
5
+ import * as Context from "effect/Context"
5
6
  import * as Effect from "effect/Effect"
6
7
  import * as Exit from "effect/Exit"
7
8
  import * as FiberRef from "effect/FiberRef"
8
9
  import { dual } from "effect/Function"
9
10
  import { globalValue } from "effect/GlobalValue"
10
11
  import * as Layer from "effect/Layer"
11
- import * as ReadonlyArray from "effect/ReadonlyArray"
12
+ import * as Option from "effect/Option"
12
13
  import * as Runtime from "effect/Runtime"
13
14
  import * as Scope from "effect/Scope"
14
15
  import * as internalMiddleware from "../internal/http/middleware.js"
16
+ import type { Middleware } from "./Middleware.js"
15
17
  import * as ServerError from "./ServerError.js"
16
18
  import * as ServerRequest from "./ServerRequest.js"
17
19
  import * as ServerResponse from "./ServerResponse.js"
@@ -32,9 +34,41 @@ export type Default<R, E> = HttpApp<R, E, ServerResponse.ServerResponse>
32
34
  * @since 1.0.0
33
35
  * @category combinators
34
36
  */
35
- export const withDefaultMiddleware = <R, E>(
36
- self: Default<R, E>
37
- ): Default<R, E> => internalMiddleware.tracer(self)
37
+ export const toHandled = <R, E, _, RH>(
38
+ self: Default<R, E>,
39
+ handleResponse: (
40
+ request: ServerRequest.ServerRequest,
41
+ exit: Exit.Exit<ServerResponse.ServerResponse, E | ServerError.ResponseError>
42
+ ) => Effect.Effect<_, never, RH>,
43
+ middleware?: Middleware | undefined
44
+ ): Default<Exclude<R | RH, Scope.Scope>, E | ServerError.ResponseError> =>
45
+ Effect.uninterruptibleMask((restore) => {
46
+ const withTracer = internalMiddleware.tracer(restore(self))
47
+ const responded = Effect.withFiberRuntime<
48
+ ServerResponse.ServerResponse,
49
+ E | ServerError.ResponseError,
50
+ R | RH | ServerRequest.ServerRequest
51
+ >((fiber) => {
52
+ const request = Context.unsafeGet(fiber.getFiberRef(FiberRef.currentContext), ServerRequest.ServerRequest)
53
+ const handler = fiber.getFiberRef(currentPreResponseHandlers)
54
+ const preHandled = handler._tag === "Some"
55
+ ? Effect.flatMap(withTracer, (response) => handler.value(request, response))
56
+ : withTracer
57
+ return Effect.flatMap(
58
+ Effect.exit(preHandled),
59
+ (exit) => {
60
+ if (exit._tag === "Failure") {
61
+ const dieOption = Cause.dieOption(exit.cause)
62
+ if (dieOption._tag === "Some" && ServerResponse.isServerResponse(dieOption.value)) {
63
+ exit = Exit.succeed(dieOption.value)
64
+ }
65
+ }
66
+ return Effect.zipRight(handleResponse(request, exit), exit)
67
+ }
68
+ )
69
+ })
70
+ return Effect.scoped(middleware === undefined ? responded : middleware(responded))
71
+ })
38
72
 
39
73
  /**
40
74
  * @since 1.0.0
@@ -49,32 +83,9 @@ export type PreResponseHandler = (
49
83
  * @since 1.0.0
50
84
  * @category fiber refs
51
85
  */
52
- export const currentPreResponseHandlers: FiberRef.FiberRef<ReadonlyArray<PreResponseHandler>> = globalValue(
86
+ export const currentPreResponseHandlers: FiberRef.FiberRef<Option.Option<PreResponseHandler>> = globalValue(
53
87
  Symbol.for("@effect/platform/Http/App/preResponseHandlers"),
54
- () => FiberRef.unsafeMake<ReadonlyArray<PreResponseHandler>>([])
55
- )
56
-
57
- function noopHandler(_request: ServerRequest.ServerRequest, response: ServerResponse.ServerResponse) {
58
- return Effect.succeed(response)
59
- }
60
-
61
- /**
62
- * @since 1.0.0
63
- * @category fiber refs
64
- */
65
- export const preResponseHandler: Effect.Effect<PreResponseHandler> = Effect.map(
66
- FiberRef.get(currentPreResponseHandlers),
67
- (handlers): PreResponseHandler =>
68
- handlers.length === 0 ?
69
- noopHandler :
70
- handlers.reduce((acc, handler) => (function(request, response) {
71
- return Effect.flatMap(
72
- acc(request, response),
73
- function(response) {
74
- return handler(request, response)
75
- }
76
- )
77
- }))
88
+ () => FiberRef.unsafeMake<Option.Option<PreResponseHandler>>(Option.none())
78
89
  )
79
90
 
80
91
  /**
@@ -86,7 +97,13 @@ export const appendPreResponseHandler: (handler: PreResponseHandler) => Effect.E
86
97
  ) =>
87
98
  FiberRef.update(
88
99
  currentPreResponseHandlers,
89
- ReadonlyArray.append(handler)
100
+ Option.match({
101
+ onNone: () => Option.some(handler),
102
+ onSome: (prev) =>
103
+ Option.some((request, response) =>
104
+ Effect.flatMap(prev(request, response), (response) => handler(request, response))
105
+ )
106
+ })
90
107
  )
91
108
 
92
109
  /**
@@ -100,7 +117,13 @@ export const withPreResponseHandler = dual<
100
117
  Effect.locallyWith(
101
118
  self,
102
119
  currentPreResponseHandlers,
103
- ReadonlyArray.append(handler)
120
+ Option.match({
121
+ onNone: () => Option.some(handler),
122
+ onSome: (prev) =>
123
+ Option.some((request, response) =>
124
+ Effect.flatMap(prev(request, response), (response) => handler(request, response))
125
+ )
126
+ })
104
127
  ))
105
128
 
106
129
  /**
@@ -110,25 +133,27 @@ export const withPreResponseHandler = dual<
110
133
  export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
111
134
  const run = Runtime.runFork(runtime)
112
135
  return <E>(self: Default<R | Scope.Scope, E>) => {
113
- self = withDefaultMiddleware(self)
136
+ const handled = Effect.scoped(toHandled(self, (request, exit) => {
137
+ const webRequest = request.source as Request
138
+ if (Exit.isSuccess(exit)) {
139
+ ;(request as any)._resolve(ServerResponse.toWeb(exit.value, request.method === "HEAD"))
140
+ } else if (Cause.isInterruptedOnly(exit.cause)) {
141
+ ;(request as any)._resolve(new Response(null, { status: webRequest.signal.aborted ? 499 : 503 }))
142
+ } else {
143
+ ;(request as any)._reject(Cause.pretty(exit.cause))
144
+ }
145
+ return Effect.unit
146
+ }))
114
147
  return (request: Request): Promise<Response> =>
115
148
  new Promise((resolve, reject) => {
116
149
  const req = ServerRequest.fromWeb(request)
117
- const fiber = run(Effect.scoped(Effect.map(
118
- Effect.provideService(self, ServerRequest.ServerRequest, req),
119
- (res) => ServerResponse.toWeb(res, req.method === "HEAD")
120
- )))
150
+ ;(req as any)._resolve = resolve
151
+ ;(req as any)._reject = reject
152
+ const fiber = run(
153
+ Effect.provideService(handled, ServerRequest.ServerRequest, req)
154
+ )
121
155
  request.signal.addEventListener("abort", () => {
122
- Effect.runFork(fiber.interruptAsFork(ServerError.clientAbortFiberId))
123
- })
124
- fiber.addObserver((exit) => {
125
- if (Exit.isSuccess(exit)) {
126
- resolve(exit.value)
127
- } else if (Cause.isInterruptedOnly(exit.cause)) {
128
- resolve(new Response(null, { status: request.signal.aborted ? 499 : 503 }))
129
- } else {
130
- reject(Cause.pretty(exit.cause))
131
- }
156
+ fiber.unsafeInterruptAsFork(ServerError.clientAbortFiberId)
132
157
  })
133
158
  })
134
159
  }
@@ -52,10 +52,9 @@ export const schemaFromSelf: Schema.Schema<Headers> = Schema.declare(isHeaders,
52
52
  */
53
53
  export const schema: Schema.Schema<Headers, ReadonlyRecord.ReadonlyRecord<string, string | ReadonlyArray<string>>> =
54
54
  Schema.transform(
55
- Schema.record(Schema.string, Schema.union(Schema.string, Schema.array(Schema.string))),
55
+ Schema.Record(Schema.String, Schema.Union(Schema.String, Schema.Array(Schema.String))),
56
56
  schemaFromSelf,
57
- (record) => fromInput(record),
58
- identity
57
+ { decode: (record) => fromInput(record), encode: identity }
59
58
  )
60
59
 
61
60
  /**
@@ -622,3 +622,10 @@ export const provideServiceEffect: {
622
622
  E | E1
623
623
  >
624
624
  } = internal.provideServiceEffect
625
+
626
+ /**
627
+ * @since 1.0.0
628
+ * @category combinators
629
+ */
630
+ export const uninterruptible: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> =
631
+ internal.uninterruptible
@@ -36,8 +36,8 @@ export const fromInput = (input: Input): UrlParams => {
36
36
  * @since 1.0.0
37
37
  * @category schemas
38
38
  */
39
- export const schema: Schema.Schema<UrlParams, ReadonlyArray<readonly [string, string]>> = Schema.array(
40
- Schema.tuple(Schema.string, Schema.string)
39
+ export const schema: Schema.Schema<UrlParams, ReadonlyArray<readonly [string, string]>> = Schema.Array(
40
+ Schema.Tuple(Schema.String, Schema.String)
41
41
  ).pipe(
42
42
  Schema.identifier("UrlParams")
43
43
  )
@@ -93,8 +93,7 @@ export const schema: {
93
93
  Schema.transformOrFail(
94
94
  Schema.encodedSchema(self),
95
95
  self,
96
- ParseResult.succeed,
97
- (i) => Effect.as(addAll(f(i)), i)
96
+ { decode: ParseResult.succeed, encode: (i) => Effect.as(addAll(f(i)), i) }
98
97
  ))
99
98
 
100
99
  /**
@@ -102,7 +101,7 @@ export const schema: {
102
101
  * @category schema
103
102
  */
104
103
  export const ImageData: Schema.Schema<ImageData> = schema(
105
- Schema.any,
104
+ Schema.Any,
106
105
  (_) => [(_ as ImageData).data.buffer]
107
106
  )
108
107
 
@@ -111,7 +110,7 @@ export const ImageData: Schema.Schema<ImageData> = schema(
111
110
  * @category schema
112
111
  */
113
112
  export const MessagePort: Schema.Schema<MessagePort> = schema(
114
- Schema.any,
113
+ Schema.Any,
115
114
  (_) => [_ as MessagePort]
116
115
  )
117
116
 
@@ -26,14 +26,16 @@ export type WorkerErrorTypeId = typeof WorkerErrorTypeId
26
26
  export const isWorkerError = (u: unknown): u is WorkerError => Predicate.hasProperty(u, WorkerErrorTypeId)
27
27
 
28
28
  const causeDefectPretty: Schema.Schema<unknown> = Schema.transform(
29
- Schema.unknown,
30
- Schema.unknown,
31
- identity,
32
- (defect) => {
33
- if (Predicate.isObject(defect)) {
34
- return Cause.pretty(Cause.die(defect))
29
+ Schema.Unknown,
30
+ Schema.Unknown,
31
+ {
32
+ decode: identity,
33
+ encode: (defect) => {
34
+ if (Predicate.isObject(defect)) {
35
+ return Cause.pretty(Cause.die(defect))
36
+ }
37
+ return String(defect)
35
38
  }
36
- return String(defect)
37
39
  }
38
40
  )
39
41
 
@@ -42,7 +44,7 @@ const causeDefectPretty: Schema.Schema<unknown> = Schema.transform(
42
44
  * @category errors
43
45
  */
44
46
  export class WorkerError extends Schema.TaggedError<WorkerError>()("WorkerError", {
45
- reason: Schema.literal("spawn", "decode", "send", "unknown", "encode"),
47
+ reason: Schema.Literal("spawn", "decode", "send", "unknown", "encode"),
46
48
  error: causeDefectPretty
47
49
  }) {
48
50
  /**
@@ -56,7 +58,7 @@ export class WorkerError extends Schema.TaggedError<WorkerError>()("WorkerError"
56
58
  static readonly Cause: Schema.Schema<
57
59
  Cause.Cause<WorkerError>,
58
60
  Schema.CauseEncoded<WorkerErrorFrom>
59
- > = Schema.cause({ defect: causeDefectPretty, error: this })
61
+ > = Schema.Cause({ defect: causeDefectPretty, error: this })
60
62
 
61
63
  /**
62
64
  * @since 1.0.0
@@ -78,18 +78,18 @@ export const logger = make((httpApp) => {
78
78
  })
79
79
 
80
80
  /** @internal */
81
- export const tracer = make((httpApp) => {
82
- const appWithStatus = Effect.tap(
83
- httpApp,
84
- (response) => Effect.annotateCurrentSpan("http.status", response.status)
85
- )
86
- return Effect.withFiberRuntime((fiber) => {
81
+ export const tracer = make((httpApp) =>
82
+ Effect.withFiberRuntime((fiber) => {
87
83
  const context = fiber.getFiberRef(FiberRef.currentContext)
88
84
  const request = Context.unsafeGet(context, ServerRequest.ServerRequest)
89
85
  const disabled = fiber.getFiberRef(currentTracerDisabledWhen)(request)
90
86
  if (disabled) {
91
87
  return httpApp
92
88
  }
89
+ const appWithStatus = Effect.tap(
90
+ httpApp,
91
+ (response) => Effect.annotateCurrentSpan("http.status", response.status)
92
+ )
93
93
  return Effect.withSpan(
94
94
  appWithStatus,
95
95
  `http.server ${request.method}`,
@@ -99,7 +99,7 @@ export const tracer = make((httpApp) => {
99
99
  }
100
100
  )
101
101
  })
102
- })
102
+ )
103
103
 
104
104
  /** @internal */
105
105
  export const xForwardedHeaders = make((httpApp) =>
@@ -105,7 +105,7 @@ const fileSchema: Schema.Schema<Multipart.PersistedFile> = Schema.declare(isPers
105
105
  })
106
106
 
107
107
  /** @internal */
108
- export const filesSchema: Schema.Schema<ReadonlyArray<Multipart.PersistedFile>> = Schema.array(fileSchema)
108
+ export const filesSchema: Schema.Schema<ReadonlyArray<Multipart.PersistedFile>> = Schema.Array(fileSchema)
109
109
 
110
110
  /** @internal */
111
111
  export const schemaPersisted = <R, I extends Partial<Multipart.Persisted>, A>(
@@ -140,7 +140,7 @@ export const schemaJson = <A, I, R>(schema: Schema.Schema<A, I, R>, options?: Pa
140
140
  >(2, (persisted, field) =>
141
141
  Effect.map(
142
142
  Schema.decodeUnknown(
143
- Schema.struct({
143
+ Schema.Struct({
144
144
  [field]: fromJson
145
145
  }),
146
146
  options
@@ -5,6 +5,7 @@ import * as Chunk from "effect/Chunk"
5
5
  import * as Context from "effect/Context"
6
6
  import * as Effect from "effect/Effect"
7
7
  import * as Effectable from "effect/Effectable"
8
+ import * as FiberRef from "effect/FiberRef"
8
9
  import { dual } from "effect/Function"
9
10
  import * as Inspectable from "effect/Inspectable"
10
11
  import * as Option from "effect/Option"
@@ -202,50 +203,48 @@ const toHttpApp = <R, E>(
202
203
  router.on(route.method, route.path, route)
203
204
  }
204
205
  })
205
- return Effect.flatMap(
206
- ServerRequest.ServerRequest,
207
- (request): App.Default<R, E | Error.RouteNotFound> => {
208
- if (mountsLen > 0) {
209
- for (let i = 0; i < mountsLen; i++) {
210
- const [path, context, options] = mounts[i]
211
- if (request.url.startsWith(path)) {
212
- return Effect.provideService(
213
- Effect.provideService(
214
- context.route.handler as App.Default<R, E>,
215
- RouteContext,
216
- context
217
- ),
218
- ServerRequest.ServerRequest,
219
- options?.includePrefix ?
220
- request :
221
- sliceRequestUrl(request, path)
222
- )
206
+ return Effect.withFiberRuntime<
207
+ ServerResponse.ServerResponse,
208
+ E | Error.RouteNotFound,
209
+ R | ServerRequest.ServerRequest
210
+ >((fiber) => {
211
+ let context = fiber.getFiberRef(FiberRef.currentContext)
212
+ const request = Context.unsafeGet(context, ServerRequest.ServerRequest)
213
+ if (mountsLen > 0) {
214
+ for (let i = 0; i < mountsLen; i++) {
215
+ const [path, routeContext, options] = mounts[i]
216
+ if (request.url.startsWith(path)) {
217
+ context = Context.add(context, RouteContext, routeContext)
218
+ if (options?.includePrefix !== true) {
219
+ context = Context.add(context, ServerRequest.ServerRequest, sliceRequestUrl(request, path))
223
220
  }
221
+ return Effect.locally(
222
+ routeContext.route.handler as App.Default<R, E>,
223
+ FiberRef.currentContext,
224
+ context
225
+ )
224
226
  }
225
227
  }
228
+ }
226
229
 
227
- let result = router.find(request.method, request.url)
228
- if (result === undefined && request.method === "HEAD") {
229
- result = router.find("GET", request.url)
230
- }
231
- if (result === undefined) {
232
- return Effect.fail(new Error.RouteNotFound({ request }))
233
- }
234
- const route = result.handler
235
- if (route.prefix._tag === "Some") {
236
- request = sliceRequestUrl(request, route.prefix.value)
237
- }
238
- return Effect.mapInputContext(
239
- route.handler as Effect.Effect<ServerResponse.ServerResponse, E, Router.Router.ExcludeProvided<R>>,
240
- (context) =>
241
- Context.add(
242
- Context.add(context, ServerRequest.ServerRequest, request),
243
- RouteContext,
244
- new RouteContextImpl(route, result!.params, result!.searchParams)
245
- ) as Context.Context<R>
246
- )
230
+ let result = router.find(request.method, request.url)
231
+ if (result === undefined && request.method === "HEAD") {
232
+ result = router.find("GET", request.url)
247
233
  }
248
- )
234
+ if (result === undefined) {
235
+ return Effect.fail(new Error.RouteNotFound({ request }))
236
+ }
237
+ const route = result.handler
238
+ if (route.prefix._tag === "Some") {
239
+ context = Context.add(context, ServerRequest.ServerRequest, sliceRequestUrl(request, route.prefix.value))
240
+ }
241
+ context = Context.add(context, RouteContext, new RouteContextImpl(route, result.params, result.searchParams))
242
+ return Effect.locally(
243
+ route.handler as Effect.Effect<ServerResponse.ServerResponse, E, Router.Router.ExcludeProvided<R>>,
244
+ FiberRef.currentContext,
245
+ context
246
+ )
247
+ })
249
248
  }
250
249
 
251
250
  function sliceRequestUrl(request: ServerRequest.ServerRequest, prefix: string) {
@@ -600,3 +599,10 @@ export const provideServiceEffect = dual<
600
599
  >,
601
600
  E | E1
602
601
  > => use(self, Effect.provideServiceEffect(tag, effect)) as any)
602
+
603
+ /* @internal */
604
+ export const uninterruptible = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
605
+ Effect.uninterruptible(Effect.flatMap(
606
+ effect,
607
+ Effect.die
608
+ ))