|
1 | | -import { Socket } from "@effect/platform" |
2 | | -import type { SocketError } from "@effect/platform/Socket" |
3 | | -import { Data, Effect, Schedule, Stream } from "effect" |
| 1 | +import { Socket } from "@effect/platform"; |
| 2 | +import type { SocketError } from "@effect/platform/Socket"; |
| 3 | +import { Cause, Effect, Schedule, Stream } from "effect"; |
4 | 4 |
|
5 | 5 | /** |
6 | | - * A stream that will reconnect to the websocket on error |
| 6 | + * A stream that will reconnect to the websocket on error. |
| 7 | + * Takes an Effect that produces the URL so it can re-evaluate |
| 8 | + * (e.g., get current cursor) on each reconnection attempt. |
7 | 9 | */ |
8 | | -const wsStream = ({ url }: { url: string | URL }) => |
9 | | - Stream.asyncPush<Uint8Array<ArrayBufferLike>, SocketError>((emit) => |
10 | | - Effect.gen(function*() { |
11 | | - yield* Effect.log("Connecting to websocket at: ", url.toString()) |
| 10 | +const wsStream = <R>({ getUrl }: { getUrl: Effect.Effect<URL, never, R> }) => |
| 11 | + Stream.asyncPush<Uint8Array<ArrayBufferLike>, SocketError, R>((emit) => |
| 12 | + Effect.gen(function* () { |
| 13 | + const url = yield* getUrl; |
| 14 | + yield* Effect.log("Connecting to websocket at: ", url.toString()); |
12 | 15 | const socket = yield* Socket.makeWebSocket(url.toString(), { |
13 | 16 | closeCodeIsError: (_) => true, |
14 | | - }) |
| 17 | + }); |
15 | 18 |
|
16 | 19 | const e = socket |
17 | 20 | .run((d) => |
18 | | - Effect.gen(function*() { |
19 | | - const didEmit = emit.single(d) |
| 21 | + Effect.gen(function* () { |
| 22 | + const didEmit = emit.single(d); |
20 | 23 | if (!didEmit) { |
| 24 | + // this doesn't seem to work because asyncPush uses an unbounded queue internally |
| 25 | + // and thus it only returns false when done. |
21 | 26 | yield* new BufferOverflowError({ |
22 | 27 | message: "Socket buffer overflowed, failed to emit a message.", |
23 | | - }) |
| 28 | + }); |
24 | 29 | } |
25 | 30 | }) |
26 | 31 | ) |
27 | 32 | .pipe( |
28 | 33 | Effect.catchTag("SocketError", (e) => Effect.succeed(emit.fail(e))), |
29 | | - Effect.fork, |
30 | | - ) |
| 34 | + Effect.fork |
| 35 | + ); |
31 | 36 |
|
32 | | - yield* e |
| 37 | + yield* e; |
33 | 38 | }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal)) |
34 | 39 | ).pipe( |
35 | 40 | Stream.tapErrorCause(Effect.logError), |
36 | | - Stream.retry(Schedule.spaced("1 second")), |
37 | | - ) |
38 | | - |
39 | | -export class BufferOverflowError extends Data.TaggedError( |
40 | | - "BufferOverflowError", |
41 | | -)<{ |
42 | | - message: string |
43 | | - cause?: unknown |
44 | | -}> {} |
| 41 | + Stream.retry(Schedule.spaced("1 second")) |
| 42 | + ); |
45 | 43 |
|
46 | 44 | export class RetryingSocket extends Effect.Service<RetryingSocket>()( |
47 | 45 | "RetryingSocket", |
48 | 46 | { |
49 | 47 | succeed: wsStream, |
50 | | - }, |
| 48 | + } |
51 | 49 | ) {} |
0 commit comments