@effect/platform 0.46.2 → 0.47.0

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 (57) hide show
  1. package/PlatformLogger/package.json +6 -0
  2. package/Socket/package.json +6 -0
  3. package/dist/cjs/Http/Router.js.map +1 -1
  4. package/dist/cjs/Http/ServerRequest.js +11 -1
  5. package/dist/cjs/Http/ServerRequest.js.map +1 -1
  6. package/dist/cjs/PlatformLogger.js +57 -0
  7. package/dist/cjs/PlatformLogger.js.map +1 -0
  8. package/dist/cjs/Socket.js +212 -0
  9. package/dist/cjs/Socket.js.map +1 -0
  10. package/dist/cjs/index.js +5 -1
  11. package/dist/cjs/internal/http/router.js +5 -4
  12. package/dist/cjs/internal/http/router.js.map +1 -1
  13. package/dist/cjs/internal/http/serverRequest.js +15 -1
  14. package/dist/cjs/internal/http/serverRequest.js.map +1 -1
  15. package/dist/cjs/internal/platformLogger.js +46 -0
  16. package/dist/cjs/internal/platformLogger.js.map +1 -0
  17. package/dist/cjs/internal/worker.js.map +1 -1
  18. package/dist/dts/Http/Router.d.ts +39 -37
  19. package/dist/dts/Http/Router.d.ts.map +1 -1
  20. package/dist/dts/Http/ServerRequest.d.ts +14 -0
  21. package/dist/dts/Http/ServerRequest.d.ts.map +1 -1
  22. package/dist/dts/PlatformLogger.d.ts +41 -0
  23. package/dist/dts/PlatformLogger.d.ts.map +1 -0
  24. package/dist/dts/Socket.d.ts +111 -0
  25. package/dist/dts/Socket.d.ts.map +1 -0
  26. package/dist/dts/index.d.ts +8 -0
  27. package/dist/dts/index.d.ts.map +1 -1
  28. package/dist/dts/internal/http/router.d.ts +1 -2
  29. package/dist/dts/internal/http/router.d.ts.map +1 -1
  30. package/dist/dts/internal/platformLogger.d.ts +2 -0
  31. package/dist/dts/internal/platformLogger.d.ts.map +1 -0
  32. package/dist/esm/Http/Router.js.map +1 -1
  33. package/dist/esm/Http/ServerRequest.js +10 -0
  34. package/dist/esm/Http/ServerRequest.js.map +1 -1
  35. package/dist/esm/PlatformLogger.js +26 -0
  36. package/dist/esm/PlatformLogger.js.map +1 -0
  37. package/dist/esm/Socket.js +166 -0
  38. package/dist/esm/Socket.js.map +1 -0
  39. package/dist/esm/index.js +8 -0
  40. package/dist/esm/index.js.map +1 -1
  41. package/dist/esm/internal/http/router.js +5 -4
  42. package/dist/esm/internal/http/router.js.map +1 -1
  43. package/dist/esm/internal/http/serverRequest.js +13 -0
  44. package/dist/esm/internal/http/serverRequest.js.map +1 -1
  45. package/dist/esm/internal/platformLogger.js +15 -0
  46. package/dist/esm/internal/platformLogger.js.map +1 -0
  47. package/dist/esm/internal/worker.js.map +1 -1
  48. package/package.json +20 -3
  49. package/src/Http/Router.ts +40 -147
  50. package/src/Http/ServerRequest.ts +25 -0
  51. package/src/PlatformLogger.ts +59 -0
  52. package/src/Socket.ts +291 -0
  53. package/src/index.ts +10 -0
  54. package/src/internal/http/router.ts +86 -38
  55. package/src/internal/http/serverRequest.ts +16 -0
  56. package/src/internal/platformLogger.ts +42 -0
  57. package/src/internal/worker.ts +5 -1
package/src/Socket.ts ADDED
@@ -0,0 +1,291 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as Cause from "effect/Cause"
5
+ import * as Channel from "effect/Channel"
6
+ import * as Chunk from "effect/Chunk"
7
+ import * as Context from "effect/Context"
8
+ import * as Data from "effect/Data"
9
+ import * as Effect from "effect/Effect"
10
+ import * as Exit from "effect/Exit"
11
+ import * as FiberSet from "effect/FiberSet"
12
+ import * as Layer from "effect/Layer"
13
+ import * as Queue from "effect/Queue"
14
+ import * as Scope from "effect/Scope"
15
+ import type * as AsyncProducer from "effect/SingleProducerAsyncInput"
16
+ import IsoWebSocket from "isomorphic-ws"
17
+
18
+ /**
19
+ * @since 1.0.0
20
+ * @category type ids
21
+ */
22
+ export const SocketTypeId = Symbol.for("@effect/platform/Socket")
23
+
24
+ /**
25
+ * @since 1.0.0
26
+ * @category type ids
27
+ */
28
+ export type SocketTypeId = typeof SocketTypeId
29
+
30
+ /**
31
+ * @since 1.0.0
32
+ * @category tags
33
+ */
34
+ export const Socket: Context.Tag<Socket, Socket> = Context.GenericTag<Socket>(
35
+ "@effect/platform/Socket"
36
+ )
37
+
38
+ /**
39
+ * @since 1.0.0
40
+ * @category models
41
+ */
42
+ export interface Socket {
43
+ readonly [SocketTypeId]: SocketTypeId
44
+ readonly run: <R, E, _>(
45
+ handler: (_: Uint8Array) => Effect.Effect<_, E, R>
46
+ ) => Effect.Effect<void, SocketError | E, R>
47
+ readonly writer: Effect.Effect<(chunk: Uint8Array) => Effect.Effect<void>, never, Scope.Scope>
48
+ }
49
+
50
+ /**
51
+ * @since 1.0.0
52
+ * @category errors
53
+ */
54
+ export class SocketError extends Data.TaggedError("SocketError")<{
55
+ readonly reason: "Write" | "Read" | "Open" | "Close"
56
+ readonly error: unknown
57
+ }> {
58
+ /**
59
+ * @since 1.0.0
60
+ */
61
+ toString(): string {
62
+ return `SocketError: ${this.reason} - ${this.error}`
63
+ }
64
+ }
65
+
66
+ /**
67
+ * @since 1.0.0
68
+ * @category combinators
69
+ */
70
+ export const toChannel = <IE>(
71
+ self: Socket
72
+ ): Channel.Channel<Chunk.Chunk<Uint8Array>, Chunk.Chunk<Uint8Array>, SocketError | IE, IE, void, unknown> =>
73
+ Channel.unwrap(
74
+ Effect.gen(function*(_) {
75
+ const writeScope = yield* _(Scope.make())
76
+ const write = yield* _(Scope.extend(self.writer, writeScope))
77
+ const exitQueue = yield* _(Queue.unbounded<Exit.Exit<Chunk.Chunk<Uint8Array>, SocketError | IE>>())
78
+
79
+ const input: AsyncProducer.AsyncInputProducer<IE, Chunk.Chunk<Uint8Array>, unknown> = {
80
+ awaitRead: () => Effect.unit,
81
+ emit(chunk) {
82
+ return Effect.catchAllCause(
83
+ Effect.forEach(chunk, write, { discard: true }),
84
+ (cause) => Queue.offer(exitQueue, Exit.failCause(cause))
85
+ )
86
+ },
87
+ error(error) {
88
+ return Effect.zipRight(
89
+ Scope.close(writeScope, Exit.unit),
90
+ Queue.offer(exitQueue, Exit.failCause(error))
91
+ )
92
+ },
93
+ done() {
94
+ return Scope.close(writeScope, Exit.unit)
95
+ }
96
+ }
97
+
98
+ yield* _(
99
+ self.run((data) => Queue.offer(exitQueue, Exit.succeed(Chunk.of(data)))),
100
+ Effect.zipRight(Effect.failCause(Cause.empty)),
101
+ Effect.exit,
102
+ Effect.tap((exit) => Queue.offer(exitQueue, exit)),
103
+ Effect.fork
104
+ )
105
+
106
+ const loop: Channel.Channel<Chunk.Chunk<Uint8Array>, unknown, SocketError | IE, unknown, void, unknown> = Channel
107
+ .flatMap(
108
+ Queue.take(exitQueue),
109
+ Exit.match({
110
+ onFailure: (cause) => Cause.isEmptyType(cause) ? Channel.unit : Channel.failCause(cause),
111
+ onSuccess: (chunk) => Channel.zipRight(Channel.write(chunk), loop)
112
+ })
113
+ )
114
+
115
+ return Channel.embedInput(loop, input)
116
+ })
117
+ )
118
+
119
+ /**
120
+ * @since 1.0.0
121
+ * @category combinators
122
+ */
123
+ export const toChannelWith = <IE = never>() =>
124
+ (
125
+ self: Socket
126
+ ): Channel.Channel<Chunk.Chunk<Uint8Array>, Chunk.Chunk<Uint8Array>, SocketError | IE, IE, void, unknown> =>
127
+ toChannel(self)
128
+
129
+ /**
130
+ * @since 1.0.0
131
+ * @category constructors
132
+ */
133
+ export const makeChannel = <IE = never>(): Channel.Channel<
134
+ Chunk.Chunk<Uint8Array>,
135
+ Chunk.Chunk<Uint8Array>,
136
+ SocketError | IE,
137
+ IE,
138
+ void,
139
+ unknown,
140
+ Socket
141
+ > => Channel.unwrap(Effect.map(Socket, toChannelWith<IE>()))
142
+
143
+ /**
144
+ * @since 1.0.0
145
+ */
146
+ export const defaultCloseCodeIsError = (code: number) => code !== 1000 && code !== 1006
147
+
148
+ /**
149
+ * @since 1.0.0
150
+ * @category tags
151
+ */
152
+ export interface WebSocket {
153
+ readonly _: unique symbol
154
+ }
155
+
156
+ /**
157
+ * @since 1.0.0
158
+ * @category tags
159
+ */
160
+ export const WebSocket: Context.Tag<WebSocket, globalThis.WebSocket> = Context.GenericTag(
161
+ "@effect/platform/Socket/WebSocket"
162
+ )
163
+
164
+ /**
165
+ * @since 1.0.0
166
+ * @category constructors
167
+ */
168
+ export const makeWebSocket = (url: string | Effect.Effect<string>, options?: {
169
+ readonly closeCodeIsError?: (code: number) => boolean
170
+ }): Effect.Effect<Socket> =>
171
+ fromWebSocket(
172
+ Effect.acquireRelease(
173
+ Effect.map(
174
+ typeof url === "string" ? Effect.succeed(url) : url,
175
+ (url) => {
176
+ const WS = "WebSocket" in globalThis ? globalThis.WebSocket : IsoWebSocket
177
+ return new WS(url) as globalThis.WebSocket
178
+ }
179
+ ),
180
+ (ws) => Effect.sync(() => ws.close())
181
+ ),
182
+ options
183
+ )
184
+
185
+ /**
186
+ * @since 1.0.0
187
+ * @category constructors
188
+ */
189
+ export const fromWebSocket = (
190
+ acquire: Effect.Effect<globalThis.WebSocket, SocketError, Scope.Scope>,
191
+ options?: {
192
+ readonly closeCodeIsError?: (code: number) => boolean
193
+ }
194
+ ): Effect.Effect<Socket> =>
195
+ Effect.gen(function*(_) {
196
+ const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
197
+ const sendQueue = yield* _(Queue.unbounded<Uint8Array>())
198
+
199
+ const run = <R, E, _>(handler: (_: Uint8Array) => Effect.Effect<_, E, R>) =>
200
+ Effect.gen(function*(_) {
201
+ const ws = yield* _(acquire)
202
+ const encoder = new TextEncoder()
203
+ const fiberSet = yield* _(FiberSet.make<any, E | SocketError>())
204
+ const run = yield* _(
205
+ FiberSet.runtime(fiberSet)<R>(),
206
+ Effect.provideService(WebSocket, ws)
207
+ )
208
+
209
+ ws.onmessage = (event) => {
210
+ run(
211
+ handler(
212
+ event.data instanceof Uint8Array
213
+ ? event.data
214
+ : typeof event.data === "string"
215
+ ? encoder.encode(event.data)
216
+ : new Uint8Array(event.data)
217
+ )
218
+ )
219
+ }
220
+
221
+ if (ws.readyState !== IsoWebSocket.OPEN) {
222
+ yield* _(Effect.async<void, SocketError, never>((resume) => {
223
+ ws.onopen = () => {
224
+ resume(Effect.unit)
225
+ }
226
+ ws.onerror = (error_) => {
227
+ resume(Effect.fail(new SocketError({ reason: "Open", error: (error_ as any).message })))
228
+ }
229
+ }))
230
+ }
231
+
232
+ yield* _(
233
+ Queue.take(sendQueue),
234
+ Effect.tap((chunk) =>
235
+ Effect.try({
236
+ try: () => ws.send(chunk),
237
+ catch: (error) => Effect.fail(new SocketError({ reason: "Write", error: (error as any).message }))
238
+ })
239
+ ),
240
+ Effect.forever,
241
+ Effect.fork
242
+ )
243
+
244
+ yield* _(
245
+ Effect.async<void, SocketError, never>((resume) => {
246
+ ws.onclose = (event) => {
247
+ if (closeCodeIsError(event.code)) {
248
+ resume(Effect.fail(new SocketError({ reason: "Close", error: event })))
249
+ } else {
250
+ resume(Effect.unit)
251
+ }
252
+ }
253
+ ws.onerror = (error) => {
254
+ resume(Effect.fail(new SocketError({ reason: "Read", error: (error as any).message })))
255
+ }
256
+ }),
257
+ Effect.raceFirst(FiberSet.join(fiberSet))
258
+ )
259
+ }).pipe(Effect.scoped)
260
+
261
+ const write = (chunk: Uint8Array) => Queue.offer(sendQueue, chunk)
262
+ const writer = Effect.succeed(write)
263
+
264
+ return Socket.of({
265
+ [SocketTypeId]: SocketTypeId,
266
+ run,
267
+ writer
268
+ })
269
+ })
270
+
271
+ /**
272
+ * @since 1.0.0
273
+ * @category constructors
274
+ */
275
+ export const makeWebSocketChannel = <IE = never>(
276
+ url: string,
277
+ options?: {
278
+ readonly closeCodeIsError?: (code: number) => boolean
279
+ }
280
+ ): Channel.Channel<Chunk.Chunk<Uint8Array>, Chunk.Chunk<Uint8Array>, SocketError | IE, IE, void, unknown> =>
281
+ Channel.unwrapScoped(
282
+ Effect.map(makeWebSocket(url, options), toChannelWith<IE>())
283
+ )
284
+
285
+ /**
286
+ * @since 1.0.0
287
+ * @category layers
288
+ */
289
+ export const layerWebSocket = (url: string, options?: {
290
+ readonly closeCodeIsError?: (code: number) => boolean
291
+ }): Layer.Layer<Socket> => Layer.scoped(Socket, makeWebSocket(url, options))
package/src/index.ts CHANGED
@@ -43,11 +43,21 @@ export * as KeyValueStore from "./KeyValueStore.js"
43
43
  */
44
44
  export * as Path from "./Path.js"
45
45
 
46
+ /**
47
+ * @since 1.0.0
48
+ */
49
+ export * as PlatformLogger from "./PlatformLogger.js"
50
+
46
51
  /**
47
52
  * @since 1.0.0
48
53
  */
49
54
  export * as Runtime from "./Runtime.js"
50
55
 
56
+ /**
57
+ * @since 1.0.0
58
+ */
59
+ export * as Socket from "./Socket.js"
60
+
51
61
  /**
52
62
  * @since 1.0.0
53
63
  */
@@ -7,6 +7,7 @@ import * as Effectable from "effect/Effectable"
7
7
  import { dual } from "effect/Function"
8
8
  import * as Inspectable from "effect/Inspectable"
9
9
  import * as Option from "effect/Option"
10
+ import * as Predicate from "effect/Predicate"
10
11
  import * as FindMyWay from "find-my-way-ts"
11
12
  import type * as App from "../../Http/App.js"
12
13
  import type * as Method from "../../Http/Method.js"
@@ -63,7 +64,9 @@ class RouterImpl<R, E> extends Effectable.StructuralClass<
63
64
  readonly [TypeId]: Router.TypeId
64
65
  constructor(
65
66
  readonly routes: Chunk.Chunk<Router.Route<R, E>>,
66
- readonly mounts: Chunk.Chunk<readonly [string, App.Default<R, E>]>
67
+ readonly mounts: Chunk.Chunk<
68
+ readonly [prefix: string, httpApp: App.Default<R, E>, options?: { readonly includePrefix?: boolean } | undefined]
69
+ >
67
70
  ) {
68
71
  super()
69
72
  this[TypeId] = TypeId
@@ -95,9 +98,24 @@ class RouterImpl<R, E> extends Effectable.StructuralClass<
95
98
 
96
99
  const toHttpApp = <R, E>(
97
100
  self: Router.Router<R, E>
98
- ): App.Default<Router.Router.ExcludeProvided<R>, E | Error.RouteNotFound> => {
101
+ ): App.Default<R, E | Error.RouteNotFound> => {
99
102
  const router = FindMyWay.make<Router.Route<R, E>>()
100
- const mounts = Chunk.toReadonlyArray(self.mounts)
103
+ const mounts = Chunk.toReadonlyArray(self.mounts).map(([path, app, options]) =>
104
+ [
105
+ path,
106
+ new RouteContextImpl(
107
+ new RouteImpl(
108
+ "*",
109
+ options?.includePrefix ? `${path}/*` as Router.PathInput : "/*",
110
+ app,
111
+ options?.includePrefix ? Option.none() : Option.some(path)
112
+ ),
113
+ {},
114
+ {}
115
+ ),
116
+ options
117
+ ] as const
118
+ )
101
119
  const mountsLen = mounts.length
102
120
  Chunk.forEach(self.routes, (route) => {
103
121
  if (route.method === "*") {
@@ -108,16 +126,22 @@ const toHttpApp = <R, E>(
108
126
  })
109
127
  return Effect.flatMap(
110
128
  ServerRequest.ServerRequest,
111
- (request): App.Default<Router.Router.ExcludeProvided<R>, E | Error.RouteNotFound> => {
129
+ (request): App.Default<R, E | Error.RouteNotFound> => {
112
130
  if (mountsLen > 0) {
113
131
  for (let i = 0; i < mountsLen; i++) {
114
- const [path, app] = mounts[i]
132
+ const [path, context, options] = mounts[i]
115
133
  if (request.url.startsWith(path)) {
116
134
  return Effect.provideService(
117
- app,
135
+ Effect.provideService(
136
+ context.route.handler as App.Default<R, E>,
137
+ RouteContext,
138
+ context
139
+ ),
118
140
  ServerRequest.ServerRequest,
119
- sliceRequestUrl(request, path)
120
- ) as App.Default<Router.Router.ExcludeProvided<R>, E>
141
+ options?.includePrefix ?
142
+ request :
143
+ sliceRequestUrl(request, path)
144
+ )
121
145
  }
122
146
  }
123
147
  }
@@ -247,19 +271,32 @@ export const mount = dual<
247
271
  export const mountApp = dual<
248
272
  <R1, E1>(
249
273
  path: `/${string}`,
250
- that: App.Default<R1, E1>
274
+ that: App.Default<R1, E1>,
275
+ options?: {
276
+ readonly includePrefix?: boolean
277
+ }
251
278
  ) => <R, E>(
252
279
  self: Router.Router<R, E>
253
- ) => Router.Router<Router.Router.ExcludeProvided<R | R1>, E | E1>,
280
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R1>, E | E1>,
254
281
  <R, E, R1, E1>(
255
282
  self: Router.Router<R, E>,
256
283
  path: `/${string}`,
257
- that: App.Default<R1, E1>
258
- ) => Router.Router<Router.Router.ExcludeProvided<R | R1>, E | E1>
284
+ that: App.Default<R1, E1>,
285
+ options?: {
286
+ readonly includePrefix?: boolean
287
+ }
288
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R1>, E | E1>
259
289
  >(
260
- 3,
261
- (self, path, that) =>
262
- new RouterImpl<any, any>(self.routes, Chunk.append(self.mounts, [removeTrailingSlash(path), that]))
290
+ (args) => Predicate.hasProperty(args[0], TypeId),
291
+ <R, E, R1, E1>(
292
+ self: Router.Router<R, E>,
293
+ path: `/${string}`,
294
+ that: App.Default<R1, E1>,
295
+ options?: {
296
+ readonly includePrefix?: boolean
297
+ }
298
+ ): Router.Router<R | Router.Router.ExcludeProvided<R1>, E | E1> =>
299
+ new RouterImpl<any, any>(self.routes, Chunk.append(self.mounts, [removeTrailingSlash(path), that, options])) as any
263
300
  )
264
301
 
265
302
  /** @internal */
@@ -269,12 +306,12 @@ export const route = (method: Method.Method | "*"): {
269
306
  handler: Router.Route.Handler<R1, E1>
270
307
  ): <R, E>(
271
308
  self: Router.Router<R, E>
272
- ) => Router.Router<Router.Router.ExcludeProvided<R | R1>, E1 | E>
309
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R1>, E1 | E>
273
310
  <R, E, R1, E1>(
274
311
  self: Router.Router<R, E>,
275
312
  path: Router.PathInput,
276
313
  handler: Router.Route.Handler<R1, E1>
277
- ): Router.Router<Router.Router.ExcludeProvided<R | R1>, E1 | E>
314
+ ): Router.Router<R | Router.Router.ExcludeProvided<R1>, E1 | E>
278
315
  } =>
279
316
  dual<
280
317
  <R1, E1>(
@@ -282,12 +319,12 @@ export const route = (method: Method.Method | "*"): {
282
319
  handler: Router.Route.Handler<R1, E1>
283
320
  ) => <R, E>(
284
321
  self: Router.Router<R, E>
285
- ) => Router.Router<Router.Router.ExcludeProvided<R | R1>, E | E1>,
322
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R1>, E | E1>,
286
323
  <R, E, R1, E1>(
287
324
  self: Router.Router<R, E>,
288
325
  path: Router.PathInput,
289
326
  handler: Router.Route.Handler<R1, E1>
290
- ) => Router.Router<Router.Router.ExcludeProvided<R | R1>, E | E1>
327
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R1>, E | E1>
291
328
  >(3, (self, path, handler) =>
292
329
  new RouterImpl<any, any>(
293
330
  Chunk.append(self.routes, new RouteImpl(method, path, handler)),
@@ -343,22 +380,22 @@ export const use = dual<
343
380
  export const catchAll = dual<
344
381
  <E, R2, E2>(
345
382
  f: (e: E) => Router.Route.Handler<R2, E2>
346
- ) => <R>(self: Router.Router<R, E>) => Router.Router<Router.Router.ExcludeProvided<R2 | R>, E2>,
383
+ ) => <R>(self: Router.Router<R, E>) => Router.Router<R | Router.Router.ExcludeProvided<R2>, E2>,
347
384
  <R, E, R2, E2>(
348
385
  self: Router.Router<R, E>,
349
386
  f: (e: E) => Router.Route.Handler<R2, E2>
350
- ) => Router.Router<Router.Router.ExcludeProvided<R2 | R>, E2>
387
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R2>, E2>
351
388
  >(2, (self, f) => use(self, Effect.catchAll(f)))
352
389
 
353
390
  /** @internal */
354
391
  export const catchAllCause = dual<
355
392
  <E, R2, E2>(
356
393
  f: (e: Cause.Cause<E>) => Router.Route.Handler<R2, E2>
357
- ) => <R>(self: Router.Router<R, E>) => Router.Router<Router.Router.ExcludeProvided<R2 | R>, E2>,
394
+ ) => <R>(self: Router.Router<R, E>) => Router.Router<R | Router.Router.ExcludeProvided<R2>, E2>,
358
395
  <R, E, R2, E2>(
359
396
  self: Router.Router<R, E>,
360
397
  f: (e: Cause.Cause<E>) => Router.Route.Handler<R2, E2>
361
- ) => Router.Router<Router.Router.ExcludeProvided<R2 | R>, E2>
398
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R2>, E2>
362
399
  >(2, (self, f) => use(self, Effect.catchAllCause(f)))
363
400
 
364
401
  /** @internal */
@@ -368,12 +405,12 @@ export const catchTag = dual<
368
405
  f: (e: Extract<E, { _tag: K }>) => Router.Route.Handler<R1, E1>
369
406
  ) => <R>(
370
407
  self: Router.Router<R, E>
371
- ) => Router.Router<Router.Router.ExcludeProvided<R | R1>, Exclude<E, { _tag: K }> | E1>,
408
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R1>, Exclude<E, { _tag: K }> | E1>,
372
409
  <R, E, K extends (E extends { _tag: string } ? E["_tag"] : never), R1, E1>(
373
410
  self: Router.Router<R, E>,
374
411
  k: K,
375
412
  f: (e: Extract<E, { _tag: K }>) => Router.Route.Handler<R1, E1>
376
- ) => Router.Router<Router.Router.ExcludeProvided<R | R1>, Exclude<E, { _tag: K }> | E1>
413
+ ) => Router.Router<R | Router.Router.ExcludeProvided<R1>, Exclude<E, { _tag: K }> | E1>
377
414
  >(3, (self, k, f) => use(self, Effect.catchTag(k, f)))
378
415
 
379
416
  /** @internal */
@@ -387,9 +424,9 @@ export const catchTags: {
387
424
  >(
388
425
  cases: Cases
389
426
  ): <R>(self: Router.Router<R, E>) => Router.Router<
390
- Router.Router.ExcludeProvided<
391
- | R
392
- | {
427
+ | R
428
+ | Router.Router.ExcludeProvided<
429
+ {
393
430
  [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, any, infer R>) ? R : never
394
431
  }[keyof Cases]
395
432
  >,
@@ -409,9 +446,9 @@ export const catchTags: {
409
446
  self: Router.Router<R, E>,
410
447
  cases: Cases
411
448
  ): Router.Router<
412
- Router.Router.ExcludeProvided<
413
- | R
414
- | {
449
+ | R
450
+ | Router.Router.ExcludeProvided<
451
+ {
415
452
  [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, any, infer R>) ? R : never
416
453
  }[keyof Cases]
417
454
  >,
@@ -428,18 +465,17 @@ export const provideService = dual<
428
465
  service: Context.Tag.Service<T>
429
466
  ) => <R, E>(
430
467
  self: Router.Router<R, E>
431
- ) => Router.Router<Router.Router.ExcludeProvided<Exclude<R, Context.Tag.Identifier<T>>>, E>,
468
+ ) => Router.Router<Exclude<R, Context.Tag.Identifier<T>>, E>,
432
469
  <R, E, T extends Context.Tag<any, any>>(
433
470
  self: Router.Router<R, E>,
434
471
  tag: T,
435
472
  service: Context.Tag.Service<T>
436
- ) => Router.Router<Router.Router.ExcludeProvided<Exclude<R, Context.Tag.Identifier<T>>>, E>
473
+ ) => Router.Router<Exclude<R, Context.Tag.Identifier<T>>, E>
437
474
  >(3, <R, E, T extends Context.Tag<any, any>>(
438
475
  self: Router.Router<R, E>,
439
476
  tag: T,
440
477
  service: Context.Tag.Service<T>
441
- ): Router.Router<Router.Router.ExcludeProvided<Exclude<R, Context.Tag.Identifier<T>>>, E> =>
442
- use(self, Effect.provideService(tag, service)))
478
+ ): Router.Router<Exclude<R, Context.Tag.Identifier<T>>, E> => use(self, Effect.provideService(tag, service)))
443
479
 
444
480
  /* @internal */
445
481
  export const provideServiceEffect = dual<
@@ -449,7 +485,10 @@ export const provideServiceEffect = dual<
449
485
  ) => <R, E>(
450
486
  self: Router.Router<R, E>
451
487
  ) => Router.Router<
452
- Router.Router.ExcludeProvided<R1 | Exclude<R, Context.Tag.Identifier<T>>>,
488
+ Exclude<
489
+ R | Router.Router.ExcludeProvided<R1>,
490
+ Context.Tag.Identifier<T>
491
+ >,
453
492
  E | E1
454
493
  >,
455
494
  <R, E, T extends Context.Tag<any, any>, R1, E1>(
@@ -457,11 +496,20 @@ export const provideServiceEffect = dual<
457
496
  tag: T,
458
497
  effect: Effect.Effect<Context.Tag.Service<T>, E1, R1>
459
498
  ) => Router.Router<
460
- Router.Router.ExcludeProvided<R1 | Exclude<R, Context.Tag.Identifier<T>>>,
499
+ Exclude<
500
+ R | Router.Router.ExcludeProvided<R1>,
501
+ Context.Tag.Identifier<T>
502
+ >,
461
503
  E | E1
462
504
  >
463
505
  >(3, <R, E, T extends Context.Tag<any, any>, R1, E1>(
464
506
  self: Router.Router<R, E>,
465
507
  tag: T,
466
508
  effect: Effect.Effect<Context.Tag.Service<T>, E1, R1>
467
- ) => use(self, Effect.provideServiceEffect(tag, effect)))
509
+ ): Router.Router<
510
+ Exclude<
511
+ R | Router.Router.ExcludeProvided<R1>,
512
+ Context.Tag.Identifier<T>
513
+ >,
514
+ E | E1
515
+ > => use(self, Effect.provideServiceEffect(tag, effect)) as any)
@@ -1,5 +1,6 @@
1
1
  import type * as ParseResult from "@effect/schema/ParseResult"
2
2
  import type * as Schema from "@effect/schema/Schema"
3
+ import * as Channel from "effect/Channel"
3
4
  import * as Context from "effect/Context"
4
5
  import * as Effect from "effect/Effect"
5
6
  import * as Option from "effect/Option"
@@ -14,6 +15,7 @@ import * as Error from "../../Http/ServerError.js"
14
15
  import type * as ServerRequest from "../../Http/ServerRequest.js"
15
16
  import * as UrlParams from "../../Http/UrlParams.js"
16
17
  import type * as Path from "../../Path.js"
18
+ import * as Socket from "../../Socket.js"
17
19
 
18
20
  /** @internal */
19
21
  export const TypeId: ServerRequest.TypeId = Symbol.for("@effect/platform/Http/ServerRequest") as ServerRequest.TypeId
@@ -21,6 +23,12 @@ export const TypeId: ServerRequest.TypeId = Symbol.for("@effect/platform/Http/Se
21
23
  /** @internal */
22
24
  export const serverRequestTag = Context.GenericTag<ServerRequest.ServerRequest>("@effect/platform/Http/ServerRequest")
23
25
 
26
+ /** @internal */
27
+ export const upgrade = Effect.flatMap(serverRequestTag, (request) => request.upgrade)
28
+
29
+ /** @internal */
30
+ export const upgradeChannel = <IE = never>() => Channel.unwrap(Effect.map(upgrade, Socket.toChannelWith<IE>()))
31
+
24
32
  /** @internal */
25
33
  export const multipartPersisted = Effect.flatMap(serverRequestTag, (request) => request.multipart)
26
34
 
@@ -252,4 +260,12 @@ class ServerRequestImpl implements ServerRequest.ServerRequest {
252
260
  ))
253
261
  return this.arrayBufferEffect
254
262
  }
263
+
264
+ get upgrade(): Effect.Effect<Socket.Socket, Error.RequestError> {
265
+ return Effect.fail(Error.RequestError({
266
+ request: this,
267
+ reason: "Decode",
268
+ error: "Not an upgradeable ServerRequest"
269
+ }))
270
+ }
255
271
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { DurationInput } from "effect/Duration"
5
+ import * as Effect from "effect/Effect"
6
+ import { dual } from "effect/Function"
7
+ import * as Logger from "effect/Logger"
8
+ import type * as Scope from "effect/Scope"
9
+ import type { PlatformError } from "../Error.js"
10
+ import * as FileSystem from "../FileSystem.js"
11
+
12
+ /** @internal */
13
+ export const toFile = dual<
14
+ (
15
+ path: string,
16
+ options?: FileSystem.OpenFileOptions & {
17
+ readonly batchWindow?: DurationInput
18
+ }
19
+ ) => <Message>(
20
+ self: Logger.Logger<Message, string>
21
+ ) => Effect.Effect<Logger.Logger<Message, void>, PlatformError, Scope.Scope | FileSystem.FileSystem>,
22
+ <Message>(
23
+ self: Logger.Logger<Message, string>,
24
+ path: string,
25
+ options?: FileSystem.OpenFileOptions & {
26
+ readonly batchWindow?: DurationInput
27
+ }
28
+ ) => Effect.Effect<Logger.Logger<Message, void>, PlatformError, Scope.Scope | FileSystem.FileSystem>
29
+ >(
30
+ (args) => Logger.isLogger(args[0]),
31
+ (self, path, options) =>
32
+ Effect.gen(function*(_) {
33
+ const fs = yield* _(FileSystem.FileSystem)
34
+ const logFile = yield* _(fs.open(path, { flag: "a+", ...options }))
35
+ const encoder = new TextEncoder()
36
+ return yield* _(Logger.batched(
37
+ self,
38
+ options?.batchWindow ?? 1000,
39
+ (output) => Effect.ignore(logFile.write(encoder.encode(output.join("\n") + "\n")))
40
+ ))
41
+ })
42
+ )
@@ -405,7 +405,11 @@ export const makePoolSerialized = <I extends Schema.TaggedRequest.Any>(
405
405
  makeSerialized<I>(options),
406
406
  Effect.tap((worker) => Effect.sync(() => workers.add(worker))),
407
407
  Effect.tap((worker) => Effect.addFinalizer(() => Effect.sync(() => workers.delete(worker)))),
408
- options.onCreate ? Effect.tap(options.onCreate) : identity,
408
+ options.onCreate
409
+ ? Effect.tap(
410
+ options.onCreate as (worker: Worker.SerializedWorker<I>) => Effect.Effect<void, WorkerError>
411
+ )
412
+ : identity,
409
413
  Effect.provideService(WorkerManager, manager)
410
414
  )
411
415
  const backing = yield* _(