-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathwebsocket.example.ts
More file actions
116 lines (104 loc) · 4.26 KB
/
Copy pathwebsocket.example.ts
File metadata and controls
116 lines (104 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { NodeSocket } from "@effect/platform-node"
import { assert, it } from "@effect/vitest"
import { Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect"
import { Socket } from "effect/unstable/socket"
import { HttpRecorder } from "effect-http-recorder"
const Room = Schema.Literals(["general", "random"])
const ChatEvent = Schema.Union([
Schema.Struct({ type: Schema.tag("join"), room: Room }),
Schema.Struct({ type: Schema.tag("message"), room: Room, text: Schema.String }),
])
const ChatEventJson = Schema.fromJsonString(ChatEvent)
const rooms = {
general: "wss://ws.postman-echo.com/raw",
random: "wss://ws.postman-echo.com/raw/",
} as const
class Chat extends Context.Service<Chat>()("example/Chat", {
make: Effect.gen(function* () {
const constructor = yield* Socket.WebSocketConstructor
const connect = Effect.fn("Chat.connect")(
function* (room: keyof typeof rooms) {
const socket = yield* Socket.makeWebSocket(rooms[room], { closeCodeIsError: () => false })
const outgoing = yield* Queue.bounded<string | Socket.CloseEvent>(16)
const incoming = yield* Stream.fromQueue(outgoing).pipe(
Stream.pipeThroughChannel(Socket.toChannelString(socket)),
Stream.mapEffect((message) => Schema.decodeEffect(ChatEventJson)(message)),
Stream.toQueue({ capacity: 16 }),
)
yield* Schema.encodeEffect(ChatEventJson)({ type: "join", room }).pipe(
Effect.flatMap((message) => Queue.offer(outgoing, message)),
)
const joined = yield* Queue.take(incoming)
if (joined.type !== "join" || joined.room !== room) return yield* Effect.die(`Failed to join ${room}`)
const messages = Stream.fromQueue(incoming).pipe(
Stream.filter((event) => event.type === "message"),
Stream.map(({ room, text }) => ({ room, text })),
)
const send = Effect.fn("ChatRoom.send")((text: string) =>
Schema.encodeEffect(ChatEventJson)({ type: "message", room, text }).pipe(
Effect.flatMap((message) => Queue.offer(outgoing, message)),
Effect.asVoid,
),
)
yield* Effect.addFinalizer(() =>
Queue.offer(outgoing, new Socket.CloseEvent(1000, `left ${room}`)).pipe(
Effect.andThen(Stream.runDrain(messages)),
Effect.orDie,
),
)
return { room, messages, send } as const
},
Effect.provideService(Socket.WebSocketConstructor, constructor),
)
return { connect } as const
}),
}) {
static readonly layer = Layer.effect(this, this.make)
}
it.effect(
"records messages across chat rooms",
() =>
Effect.gen(function* () {
const chat = yield* Chat
yield* Effect.scoped(
Effect.gen(function* () {
const general = yield* chat.connect("general")
const random = yield* chat.connect("random")
const generalMessages = yield* general.messages.pipe(
Stream.take(3),
Stream.runCollect,
Effect.forkScoped({ startImmediately: true }),
)
const randomMessages = yield* random.messages.pipe(
Stream.take(3),
Stream.runCollect,
Effect.forkScoped({ startImmediately: true }),
)
yield* general.send("Hello!")
yield* random.send("Did you see that?")
yield* general.send("Anyone around?")
yield* random.send("Incredible")
yield* general.send("See you later")
yield* random.send("Wow")
assert.deepStrictEqual(yield* Fiber.join(generalMessages), [
{ room: "general", text: "Hello!" },
{ room: "general", text: "Anyone around?" },
{ room: "general", text: "See you later" },
])
assert.deepStrictEqual(yield* Fiber.join(randomMessages), [
{ room: "random", text: "Did you see that?" },
{ room: "random", text: "Incredible" },
{ room: "random", text: "Wow" },
])
}),
)
}).pipe(
Effect.provide(
Chat.layer.pipe(
Layer.provide(HttpRecorder.layerWebSocketConstructor("websocket-chat", { directory: "examples/recordings" })),
Layer.provide(NodeSocket.layerWebSocketConstructor),
),
),
),
30_000,
)