dfx 0.77.3 → 0.79.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 (139) hide show
  1. package/Cache/prelude.d.ts +3 -3
  2. package/DiscordConfig.d.ts +8 -10
  3. package/DiscordConfig.d.ts.map +1 -1
  4. package/DiscordConfig.js +1 -3
  5. package/DiscordConfig.js.map +1 -1
  6. package/DiscordGateway/DiscordWS.d.ts +12 -11
  7. package/DiscordGateway/DiscordWS.d.ts.map +1 -1
  8. package/DiscordGateway/DiscordWS.js +8 -5
  9. package/DiscordGateway/DiscordWS.js.map +1 -1
  10. package/DiscordGateway/Messaging.d.ts +28 -0
  11. package/DiscordGateway/Messaging.d.ts.map +1 -0
  12. package/DiscordGateway/Messaging.js +41 -0
  13. package/DiscordGateway/Messaging.js.map +1 -0
  14. package/DiscordGateway/Shard.d.ts +14 -11
  15. package/DiscordGateway/Shard.d.ts.map +1 -1
  16. package/DiscordGateway/Shard.js +22 -19
  17. package/DiscordGateway/Shard.js.map +1 -1
  18. package/DiscordGateway/ShardStore.d.ts +5 -2
  19. package/DiscordGateway/ShardStore.d.ts.map +1 -1
  20. package/DiscordGateway/ShardStore.js +1 -1
  21. package/DiscordGateway/ShardStore.js.map +1 -1
  22. package/DiscordGateway/Sharder.d.ts +9 -18
  23. package/DiscordGateway/Sharder.d.ts.map +1 -1
  24. package/DiscordGateway/Sharder.js +36 -45
  25. package/DiscordGateway/Sharder.js.map +1 -1
  26. package/DiscordGateway/WS.d.ts +14 -10
  27. package/DiscordGateway/WS.d.ts.map +1 -1
  28. package/DiscordGateway/WS.js +31 -21
  29. package/DiscordGateway/WS.js.map +1 -1
  30. package/DiscordGateway.d.ts +10 -7
  31. package/DiscordGateway.d.ts.map +1 -1
  32. package/DiscordGateway.js +13 -31
  33. package/DiscordGateway.js.map +1 -1
  34. package/DiscordREST.d.ts +6 -4
  35. package/DiscordREST.d.ts.map +1 -1
  36. package/DiscordREST.js +9 -8
  37. package/DiscordREST.js.map +1 -1
  38. package/Interactions/context.d.ts +36 -18
  39. package/Interactions/context.d.ts.map +1 -1
  40. package/Interactions/context.js +6 -6
  41. package/Interactions/context.js.map +1 -1
  42. package/Interactions/definitions.d.ts +11 -11
  43. package/Interactions/definitions.d.ts.map +1 -1
  44. package/Interactions/definitions.js.map +1 -1
  45. package/Interactions/gateway.d.ts +7 -6
  46. package/Interactions/gateway.d.ts.map +1 -1
  47. package/Interactions/gateway.js +1 -1
  48. package/Interactions/gateway.js.map +1 -1
  49. package/Interactions/handlers.d.ts +2 -1
  50. package/Interactions/handlers.d.ts.map +1 -1
  51. package/Interactions/webhook.d.ts +7 -8
  52. package/Interactions/webhook.d.ts.map +1 -1
  53. package/Interactions/webhook.js +3 -3
  54. package/Interactions/webhook.js.map +1 -1
  55. package/README.md +37 -29
  56. package/RateLimit/memory.d.ts +2 -2
  57. package/RateLimit/memory.d.ts.map +1 -1
  58. package/RateLimit/memory.js.map +1 -1
  59. package/RateLimit.d.ts +10 -9
  60. package/RateLimit.d.ts.map +1 -1
  61. package/RateLimit.js +4 -5
  62. package/RateLimit.js.map +1 -1
  63. package/gateway.d.ts +2 -2
  64. package/gateway.d.ts.map +1 -1
  65. package/gateway.js +1 -2
  66. package/gateway.js.map +1 -1
  67. package/index.d.ts +1 -2
  68. package/index.d.ts.map +1 -1
  69. package/index.js +1 -3
  70. package/index.js.map +1 -1
  71. package/mjs/DiscordConfig.mjs +1 -3
  72. package/mjs/DiscordConfig.mjs.map +1 -1
  73. package/mjs/DiscordGateway/DiscordWS.mjs +8 -5
  74. package/mjs/DiscordGateway/DiscordWS.mjs.map +1 -1
  75. package/mjs/DiscordGateway/Messaging.mjs +33 -0
  76. package/mjs/DiscordGateway/Messaging.mjs.map +1 -0
  77. package/mjs/DiscordGateway/Shard.mjs +22 -19
  78. package/mjs/DiscordGateway/Shard.mjs.map +1 -1
  79. package/mjs/DiscordGateway/ShardStore.mjs +1 -1
  80. package/mjs/DiscordGateway/ShardStore.mjs.map +1 -1
  81. package/mjs/DiscordGateway/Sharder.mjs +35 -44
  82. package/mjs/DiscordGateway/Sharder.mjs.map +1 -1
  83. package/mjs/DiscordGateway/WS.mjs +32 -22
  84. package/mjs/DiscordGateway/WS.mjs.map +1 -1
  85. package/mjs/DiscordGateway.mjs +12 -30
  86. package/mjs/DiscordGateway.mjs.map +1 -1
  87. package/mjs/DiscordREST.mjs +9 -8
  88. package/mjs/DiscordREST.mjs.map +1 -1
  89. package/mjs/Interactions/context.mjs +6 -6
  90. package/mjs/Interactions/context.mjs.map +1 -1
  91. package/mjs/Interactions/definitions.mjs.map +1 -1
  92. package/mjs/Interactions/gateway.mjs +1 -1
  93. package/mjs/Interactions/gateway.mjs.map +1 -1
  94. package/mjs/Interactions/webhook.mjs +3 -3
  95. package/mjs/Interactions/webhook.mjs.map +1 -1
  96. package/mjs/RateLimit/memory.mjs.map +1 -1
  97. package/mjs/RateLimit.mjs +4 -5
  98. package/mjs/RateLimit.mjs.map +1 -1
  99. package/mjs/gateway.mjs +1 -2
  100. package/mjs/gateway.mjs.map +1 -1
  101. package/mjs/index.mjs +1 -2
  102. package/mjs/index.mjs.map +1 -1
  103. package/mjs/version.mjs +1 -1
  104. package/mjs/webhooks.mjs +1 -2
  105. package/mjs/webhooks.mjs.map +1 -1
  106. package/package.json +4 -4
  107. package/src/DiscordConfig.ts +10 -12
  108. package/src/DiscordGateway/DiscordWS.ts +23 -13
  109. package/src/DiscordGateway/Messaging.ts +72 -0
  110. package/src/DiscordGateway/Shard.ts +53 -35
  111. package/src/DiscordGateway/ShardStore.ts +8 -3
  112. package/src/DiscordGateway/Sharder.ts +72 -97
  113. package/src/DiscordGateway/WS.ts +64 -32
  114. package/src/DiscordGateway.ts +22 -71
  115. package/src/DiscordREST.ts +42 -29
  116. package/src/Interactions/context.ts +47 -10
  117. package/src/Interactions/definitions.ts +22 -20
  118. package/src/Interactions/gateway.ts +14 -6
  119. package/src/Interactions/handlers.ts +1 -1
  120. package/src/Interactions/webhook.ts +21 -7
  121. package/src/RateLimit/memory.ts +2 -2
  122. package/src/RateLimit.ts +17 -8
  123. package/src/gateway.ts +0 -2
  124. package/src/index.ts +0 -2
  125. package/src/version.ts +1 -1
  126. package/src/webhooks.ts +1 -2
  127. package/version.d.ts +1 -1
  128. package/version.js +1 -1
  129. package/webhooks.d.ts +1 -2
  130. package/webhooks.d.ts.map +1 -1
  131. package/webhooks.js +1 -2
  132. package/webhooks.js.map +1 -1
  133. package/Log.d.ts +0 -14
  134. package/Log.d.ts.map +0 -1
  135. package/Log.js +0 -23
  136. package/Log.js.map +0 -1
  137. package/mjs/Log.mjs +0 -15
  138. package/mjs/Log.mjs.map +0 -1
  139. package/src/Log.ts +0 -24
@@ -3,7 +3,7 @@ import { Tag } from "effect/Context"
3
3
  import * as Duration from "effect/Duration"
4
4
  import { pipe } from "effect/Function"
5
5
  import * as Option from "effect/Option"
6
- import * as ConfigSecret from "effect/ConfigSecret"
6
+ import * as Secret from "effect/Secret"
7
7
  import * as Effect from "effect/Effect"
8
8
  import * as PubSub from "effect/PubSub"
9
9
  import * as Layer from "effect/Layer"
@@ -17,9 +17,9 @@ import * as Identify from "dfx/DiscordGateway/Shard/identify"
17
17
  import * as InvalidSession from "dfx/DiscordGateway/Shard/invalidSession"
18
18
  import * as Utils from "dfx/DiscordGateway/Shard/utils"
19
19
  import { Reconnect } from "dfx/DiscordGateway/WS"
20
- import { Log } from "dfx/Log"
21
20
  import { RateLimiterLive, RateLimiter } from "dfx/RateLimit"
22
21
  import * as Discord from "dfx/types"
22
+ import { Messaging, MesssagingLive } from "dfx/DiscordGateway/Messaging"
23
23
 
24
24
  const enum Phase {
25
25
  Connecting,
@@ -31,19 +31,18 @@ export const make = Effect.gen(function* (_) {
31
31
  const { gateway, token } = yield* _(DiscordConfig)
32
32
  const limiter = yield* _(RateLimiter)
33
33
  const dws = yield* _(DiscordWS)
34
- const log = yield* _(Log)
34
+ const { hub, sendQueue } = yield* _(Messaging)
35
35
 
36
- const connect = (
37
- shard: [id: number, count: number],
38
- hub: PubSub.PubSub<Discord.GatewayPayload<Discord.ReceiveEvent>>,
39
- sendQueue: Queue.Dequeue<Discord.GatewayPayload<Discord.SendEvent>>,
40
- ) =>
36
+ const connect = (shard: [id: number, count: number]) =>
41
37
  Effect.gen(function* (_) {
42
38
  const outboundQueue = yield* _(Queue.unbounded<Message>())
43
39
  const pendingQueue = yield* _(Queue.unbounded<Message>())
44
40
  const phase = yield* _(Ref.make(Phase.Connecting))
45
41
  const setPhase = (p: Phase) =>
46
- Effect.zipLeft(Ref.set(phase, p), log.debug("Shard", shard, "phase", p))
42
+ Effect.zipLeft(
43
+ Ref.set(phase, p),
44
+ Effect.annotateLogs(Effect.logTrace("phase transition"), "phase", p),
45
+ )
47
46
  const outbound = Effect.zipLeft(
48
47
  Queue.take(outboundQueue),
49
48
  limiter.maybeWait("dfx.shard.send", Duration.minutes(1), 120),
@@ -115,21 +114,29 @@ export const make = Effect.gen(function* (_) {
115
114
  },
116
115
  )
117
116
 
118
- const hellos = yield* _(Queue.unbounded<Discord.GatewayPayload>())
119
- const acks = yield* _(Queue.unbounded<Discord.GatewayPayload>())
117
+ const hellos = yield* _(
118
+ Effect.acquireRelease(
119
+ Queue.unbounded<Discord.GatewayPayload>(),
120
+ Queue.shutdown,
121
+ ),
122
+ )
123
+ const acks = yield* _(
124
+ Effect.acquireRelease(
125
+ Queue.unbounded<Discord.GatewayPayload>(),
126
+ Queue.shutdown,
127
+ ),
128
+ )
120
129
 
121
130
  // heartbeats
122
- const heartbeats = Heartbeats.send(
123
- hellos,
124
- acks,
125
- latestSequence,
126
- heartbeatSend,
131
+ yield* _(
132
+ Heartbeats.send(hellos, acks, latestSequence, heartbeatSend),
133
+ Effect.forkScoped,
127
134
  )
128
135
 
129
136
  // identify
130
137
  const identify = Identify.identifyOrResume(
131
138
  {
132
- token: ConfigSecret.value(token),
139
+ token: Secret.value(token),
133
140
  shard,
134
141
  intents: gateway.intents,
135
142
  presence: gateway.presence,
@@ -169,32 +176,43 @@ export const make = Effect.gen(function* (_) {
169
176
  }),
170
177
  )
171
178
 
172
- const drainSendQueue = Effect.forever(
173
- Effect.tap(Queue.take(sendQueue), send),
179
+ yield* _(
180
+ Queue.take(sendQueue),
181
+ Effect.tap(send),
182
+ Effect.forever,
183
+ Effect.forkScoped,
174
184
  )
175
185
 
176
- const run = Effect.all(
177
- [
178
- Effect.forever(Effect.flatMap(socket.take, onPayload)),
179
- heartbeats,
180
- drainSendQueue,
181
- socket.run,
182
- ],
183
- { discard: true, concurrency: "unbounded" },
186
+ yield* _(
187
+ socket.take,
188
+ Effect.flatMap(onPayload),
189
+ Effect.forever,
190
+ Effect.forkScoped,
184
191
  )
185
192
 
186
- return { id: shard, send, run } as const
187
- })
193
+ return { id: shard, send } as const
194
+ }).pipe(
195
+ Effect.annotateLogs({
196
+ package: "dfx",
197
+ module: "DiscordGateway/Shard",
198
+ shard,
199
+ }),
200
+ )
188
201
 
189
202
  return { connect } as const
190
203
  })
191
204
 
192
- export interface Shard extends Effect.Effect.Success<typeof make> {}
193
- export const Shard = Tag<Shard>()
194
- export const ShardLive = Layer.provide(
195
- Layer.effect(Shard, make),
196
- Layer.merge(DiscordWSLive, RateLimiterLive),
205
+ type ShardService = Effect.Effect.Success<typeof make>
206
+
207
+ export interface Shard {
208
+ readonly _: unique symbol
209
+ }
210
+ export const Shard = Tag<Shard, ShardService>("dfx/DiscordGateway/Shard")
211
+ export const ShardLive = Layer.effect(Shard, make).pipe(
212
+ Layer.provide(DiscordWSLive),
213
+ Layer.provide(MesssagingLive),
214
+ Layer.provide(RateLimiterLive),
197
215
  )
198
216
 
199
217
  export interface RunningShard
200
- extends Effect.Effect.Success<ReturnType<Shard["connect"]>> {}
218
+ extends Effect.Effect.Success<ReturnType<ShardService["connect"]>> {}
@@ -8,17 +8,22 @@ export interface ClaimIdContext {
8
8
  totalCount: number
9
9
  }
10
10
 
11
- export interface ShardStore {
11
+ export interface ShardStoreService {
12
12
  claimId: (
13
13
  ctx: ClaimIdContext,
14
14
  ) => Effect.Effect<never, never, Option.Option<number>>
15
15
  allClaimed: (totalCount: number) => Effect.Effect<never, never, boolean>
16
16
  heartbeat?: (shardId: number) => Effect.Effect<never, never, void>
17
17
  }
18
- export const ShardStore = Tag<ShardStore>()
18
+ export interface ShardStore {
19
+ readonly _: unique symbol
20
+ }
21
+ export const ShardStore = Tag<ShardStore, ShardStoreService>(
22
+ "dfx/DiscordGateway/ShardStore",
23
+ )
19
24
 
20
25
  // Very basic shard id store, that does no health checks
21
- const memoryStore = (): ShardStore => {
26
+ const memoryStore = (): ShardStoreService => {
22
27
  let currentId = 0
23
28
 
24
29
  return {
@@ -1,24 +1,19 @@
1
- import * as Chunk from "effect/Chunk"
1
+ import { DiscordConfig } from "dfx/DiscordConfig"
2
+ import type { RunningShard } from "dfx/DiscordGateway/Shard"
3
+ import { Shard, ShardLive } from "dfx/DiscordGateway/Shard"
4
+ import { ShardStore } from "dfx/DiscordGateway/ShardStore"
5
+ import { DiscordREST, DiscordRESTLive } from "dfx/DiscordREST"
6
+ import { RateLimiter, RateLimiterLive } from "dfx/RateLimit"
7
+ import type * as Discord from "dfx/types"
2
8
  import { Tag } from "effect/Context"
3
9
  import * as Duration from "effect/Duration"
10
+ import * as Effect from "effect/Effect"
4
11
  import { pipe } from "effect/Function"
5
12
  import * as HashSet from "effect/HashSet"
6
- import type * as Option from "effect/Option"
7
- import * as Deferred from "effect/Deferred"
8
- import * as Effect from "effect/Effect"
9
- import type * as PubSub from "effect/PubSub"
10
13
  import * as Layer from "effect/Layer"
11
- import type * as Queue from "effect/Queue"
14
+ import type * as Option from "effect/Option"
12
15
  import * as Ref from "effect/Ref"
13
16
  import * as Schedule from "effect/Schedule"
14
- import { DiscordConfig } from "dfx/DiscordConfig"
15
- import type { RunningShard } from "dfx/DiscordGateway/Shard"
16
- import { ShardLive, Shard } from "dfx/DiscordGateway/Shard"
17
- import { ShardStore } from "dfx/DiscordGateway/ShardStore"
18
- import type { WebSocketCloseError, WebSocketError } from "dfx/DiscordGateway/WS"
19
- import { DiscordREST } from "dfx/DiscordREST"
20
- import { RateLimiterLive, RateLimiter } from "dfx/RateLimit"
21
- import type * as Discord from "dfx/types"
22
17
 
23
18
  const claimRepeatPolicy = Schedule.spaced("3 minutes").pipe(
24
19
  Schedule.whileInput((_: Option.Option<number>) => _._tag === "None"),
@@ -33,29 +28,6 @@ const make = Effect.gen(function* (_) {
33
28
  const shard = yield* _(Shard)
34
29
  const currentShards = yield* _(Ref.make(HashSet.empty<RunningShard>()))
35
30
 
36
- const takeConfig = (totalCount: number) =>
37
- Effect.gen(function* (_) {
38
- const currentCount = yield* _(Ref.make(0))
39
-
40
- const claimId = (
41
- sharderCount: number,
42
- ): Effect.Effect<never, never, number> =>
43
- pipe(
44
- store.claimId({
45
- totalCount,
46
- sharderCount,
47
- }),
48
- Effect.repeat(claimRepeatPolicy),
49
- Effect.map(_ => _.value),
50
- )
51
-
52
- return pipe(
53
- Ref.getAndUpdate(currentCount, _ => _ + 1),
54
- Effect.flatMap(claimId),
55
- Effect.map(id => ({ id, totalCount }) as const),
56
- )
57
- })
58
-
59
31
  const gateway = yield* _(
60
32
  rest.getGatewayBot(),
61
33
  Effect.flatMap(r => r.json),
@@ -73,68 +45,71 @@ const make = Effect.gen(function* (_) {
73
45
  ),
74
46
  )
75
47
 
76
- const run = (
77
- hub: PubSub.PubSub<Discord.GatewayPayload<Discord.ReceiveEvent>>,
78
- sendQueue: Queue.Dequeue<Discord.GatewayPayload<Discord.SendEvent>>,
79
- ) =>
80
- Effect.gen(function* (_) {
81
- const deferred = yield* _(
82
- Deferred.make<WebSocketError | WebSocketCloseError, never>(),
83
- )
84
- const take = yield* _(takeConfig(config.shardCount ?? gateway.shards))
85
-
86
- const spawner = pipe(
87
- take,
88
- Effect.map(config => ({
89
- ...config,
90
- url: gateway.url,
91
- concurrency: gateway.session_start_limit.max_concurrency,
92
- })),
93
- Effect.tap(({ concurrency, id }) =>
94
- limiter.maybeWait(
95
- `dfx.sharder.${id % concurrency}`,
96
- Duration.millis(config.identifyRateLimit[0]),
97
- config.identifyRateLimit[1],
98
- ),
99
- ),
100
- Effect.flatMap(c =>
101
- shard.connect([c.id, c.totalCount], hub, sendQueue),
102
- ),
103
- Effect.flatMap(shard =>
104
- Effect.acquireUseRelease(
105
- Ref.update(currentShards, HashSet.add(shard)),
106
- () => shard.run,
107
- () => Ref.update(currentShards, HashSet.remove(shard)),
108
- ).pipe(
109
- Effect.catchAllCause(_ => Deferred.failCause(deferred, _)),
110
- Effect.fork,
111
- ),
112
- ),
113
- Effect.forever,
114
- )
48
+ const totalCount = config.shardCount ?? gateway.shards
49
+ const currentCount = yield* _(Ref.make(0))
50
+ const claimId = (sharderCount: number): Effect.Effect<never, never, number> =>
51
+ pipe(
52
+ store.claimId({
53
+ totalCount,
54
+ sharderCount,
55
+ }),
56
+ Effect.repeat(claimRepeatPolicy),
57
+ Effect.map(_ => _.value),
58
+ )
59
+ const takeConfig = pipe(
60
+ Ref.getAndUpdate(currentCount, _ => _ + 1),
61
+ Effect.flatMap(claimId),
62
+ Effect.map(id => ({ id, totalCount }) as const),
63
+ )
115
64
 
116
- const spawners = Chunk.map(
117
- Chunk.range(1, gateway.session_start_limit.max_concurrency),
118
- () => spawner,
119
- )
65
+ const spawner = pipe(
66
+ takeConfig,
67
+ Effect.map(config => ({
68
+ ...config,
69
+ url: gateway.url,
70
+ concurrency: gateway.session_start_limit.max_concurrency,
71
+ })),
72
+ Effect.tap(({ concurrency, id }) =>
73
+ limiter.maybeWait(
74
+ `dfx.sharder.${id % concurrency}`,
75
+ Duration.millis(config.identifyRateLimit[0]),
76
+ config.identifyRateLimit[1],
77
+ ),
78
+ ),
79
+ Effect.flatMap(c => shard.connect([c.id, c.totalCount])),
80
+ Effect.flatMap(shard => Ref.update(currentShards, HashSet.add(shard))),
81
+ Effect.forever,
82
+ )
120
83
 
121
- return yield* _(
122
- Effect.all(
123
- [
124
- Effect.all(spawners, { concurrency: "unbounded", discard: true }),
125
- Deferred.await(deferred),
126
- ],
127
- { concurrency: "unbounded", discard: true },
128
- ) as Effect.Effect<never, WebSocketError | WebSocketCloseError, never>,
129
- )
130
- })
84
+ yield* _(
85
+ Effect.replicateEffect(
86
+ spawner,
87
+ gateway.session_start_limit.max_concurrency,
88
+ { concurrency: "unbounded", discard: true },
89
+ ),
90
+ Effect.scoped,
91
+ Effect.catchAllCause(Effect.logError),
92
+ Effect.ensuring(Ref.set(currentShards, HashSet.empty())),
93
+ Effect.forever,
94
+ Effect.forkScoped,
95
+ )
131
96
 
132
- return { shards: Ref.get(currentShards), run } as const
133
- })
97
+ return { shards: Ref.get(currentShards) } as const
98
+ }).pipe(
99
+ Effect.annotateLogs({
100
+ package: "dfx",
101
+ module: "DiscordGateway/Sharder",
102
+ }),
103
+ )
134
104
 
135
- export interface Sharder extends Effect.Effect.Success<typeof make> {}
136
- export const Sharder = Tag<Sharder>()
137
- export const SharedLive = Layer.provide(
138
- Layer.effect(Sharder, make),
139
- Layer.merge(RateLimiterLive, ShardLive),
105
+ export interface Sharder {
106
+ readonly _: unique symbol
107
+ }
108
+ export const Sharder = Tag<Sharder, Effect.Effect.Success<typeof make>>(
109
+ "dfx/DiscordGateway/Sharder",
110
+ )
111
+ export const SharderLive = Layer.scoped(Sharder, make).pipe(
112
+ Layer.provide(DiscordRESTLive),
113
+ Layer.provide(RateLimiterLive),
114
+ Layer.provide(ShardLive),
140
115
  )
@@ -1,12 +1,13 @@
1
1
  import { Tag } from "effect/Context"
2
- import * as Duration from "effect/Duration"
3
- import { pipe } from "effect/Function"
2
+ import type * as Duration from "effect/Duration"
3
+ import { identity, pipe } from "effect/Function"
4
4
  import * as Effect from "effect/Effect"
5
5
  import * as Layer from "effect/Layer"
6
6
  import * as Queue from "effect/Queue"
7
7
  import * as Ref from "effect/Ref"
8
- import { Log } from "dfx/Log"
9
8
  import WebSocket from "isomorphic-ws"
9
+ import type { Predicate } from "effect/Predicate"
10
+ import * as Schedule from "effect/Schedule"
10
11
 
11
12
  export const Reconnect = Symbol()
12
13
  export type Reconnect = typeof Reconnect
@@ -49,16 +50,10 @@ const socket = (urlRef: Ref.Ref<string>) =>
49
50
  const offer = (
50
51
  ws: globalThis.WebSocket,
51
52
  queue: Queue.Enqueue<WebSocket.Data>,
52
- log: Log,
53
53
  ) =>
54
54
  Effect.async<never, WebSocketError | WebSocketCloseError, never>(resume => {
55
55
  ws.addEventListener("message", message => {
56
- Effect.runFork(
57
- Effect.zipRight(
58
- log.debug("WS", "receive", message.data),
59
- Queue.offer(queue, message.data),
60
- ),
61
- )
56
+ Queue.unsafeOffer(queue, message.data)
62
57
  })
63
58
 
64
59
  ws.addEventListener("error", cause => {
@@ -70,7 +65,10 @@ const offer = (
70
65
  })
71
66
  })
72
67
 
73
- const waitForOpen = (ws: globalThis.WebSocket, timeout: Duration.Duration) =>
68
+ const waitForOpen = (
69
+ ws: globalThis.WebSocket,
70
+ timeout: Duration.DurationInput,
71
+ ) =>
74
72
  Effect.timeoutFail(
75
73
  Effect.suspend(() => {
76
74
  if (ws.readyState === WebSocket.OPEN) {
@@ -92,11 +90,10 @@ const waitForOpen = (ws: globalThis.WebSocket, timeout: Duration.Duration) =>
92
90
  const send = (
93
91
  ws: globalThis.WebSocket,
94
92
  take: Effect.Effect<never, never, Message>,
95
- log: Log,
96
93
  ) =>
97
94
  pipe(
98
95
  take,
99
- Effect.tap(data => log.debug("WS", "send", data)),
96
+ Effect.tap(data => Effect.logTrace(data)),
100
97
  Effect.tap(data => {
101
98
  if (data === Reconnect) {
102
99
  return Effect.failSync(() => {
@@ -110,30 +107,50 @@ const send = (
110
107
  })
111
108
  }),
112
109
  Effect.forever,
110
+ Effect.annotateLogs("method", "send"),
113
111
  )
114
112
 
115
- const make = Effect.gen(function* (_) {
116
- const log = yield* _(Log)
117
-
118
- const connect = (
119
- url: Ref.Ref<string>,
120
- takeOutbound: Effect.Effect<never, never, Message>,
113
+ const wsImpl = {
114
+ connect: ({
121
115
  onConnecting = Effect.unit,
122
- openTimeout = Duration.seconds(3),
123
- ) =>
116
+ openTimeout = "3 seconds",
117
+ reconnectWhen,
118
+ takeOutbound,
119
+ urlRef,
120
+ }: {
121
+ readonly urlRef: Ref.Ref<string>
122
+ readonly takeOutbound: Effect.Effect<never, never, Message>
123
+ readonly onConnecting?: Effect.Effect<never, never, void>
124
+ readonly openTimeout?: Duration.DurationInput
125
+ readonly reconnectWhen?: Predicate<WebSocketError | WebSocketCloseError>
126
+ }) =>
124
127
  Effect.gen(function* (_) {
125
- const queue = yield* _(Queue.unbounded<WebSocket.Data>())
128
+ const scope = yield* _(Effect.scope)
129
+ const queue = yield* _(
130
+ Effect.acquireRelease(
131
+ Queue.unbounded<WebSocket.Data>(),
132
+ Queue.shutdown,
133
+ ),
134
+ )
135
+ const take = Effect.annotateLogs(
136
+ Effect.tap(Queue.take(queue), data => Effect.logTrace(data)),
137
+ {
138
+ package: "dfx",
139
+ module: "DiscordGateway/WS",
140
+ method: "take",
141
+ },
142
+ )
126
143
 
127
144
  const run = pipe(
128
145
  onConnecting,
129
- Effect.zipRight(socket(url)),
146
+ Effect.zipRight(socket(urlRef)),
130
147
  Effect.flatMap(ws =>
131
148
  Effect.all(
132
149
  [
133
- offer(ws, queue, log),
150
+ offer(ws, queue),
134
151
  Effect.zipRight(
135
152
  waitForOpen(ws, openTimeout),
136
- send(ws, takeOutbound, log),
153
+ send(ws, takeOutbound),
137
154
  ),
138
155
  ],
139
156
  { concurrency: "unbounded", discard: true },
@@ -141,14 +158,29 @@ const make = Effect.gen(function* (_) {
141
158
  ),
142
159
  Effect.scoped,
143
160
  Effect.retryWhile(isReconnect),
161
+ reconnectWhen ? Effect.retryWhile(reconnectWhen) : identity,
162
+ Effect.catchAllCause(Effect.logError),
163
+ Effect.repeat(
164
+ Schedule.exponential("500 millis").pipe(
165
+ Schedule.union(Schedule.spaced("30 seconds")),
166
+ ),
167
+ ),
168
+ Effect.forkIn(scope),
144
169
  )
145
170
 
146
- return { run, take: Queue.take(queue) } as const
147
- })
171
+ yield* _(run)
148
172
 
149
- return { connect } as const
150
- })
173
+ return { take } as const
174
+ }).pipe(
175
+ Effect.annotateLogs({
176
+ package: "dfx",
177
+ module: "DiscordGateway/WS",
178
+ }),
179
+ ),
180
+ } as const
151
181
 
152
- export interface WS extends Effect.Effect.Success<typeof make> {}
153
- export const WS = Tag<WS>()
154
- export const WSLive = Layer.effect(WS, make)
182
+ export interface WS {
183
+ readonly _: unique symbol
184
+ }
185
+ export const WS = Tag<WS, typeof wsImpl>("dfx/DiscordGateway/WS")
186
+ export const WSLive = Layer.succeed(WS, wsImpl)
@@ -1,42 +1,19 @@
1
+ import { Messaging, MesssagingLive } from "dfx/DiscordGateway/Messaging"
2
+ import type { RunningShard } from "dfx/DiscordGateway/Shard"
3
+ import { Sharder, SharderLive } from "dfx/DiscordGateway/Sharder"
4
+ import type * as Discord from "dfx/types"
1
5
  import { Tag } from "effect/Context"
2
- import type * as HashSet from "effect/HashSet"
3
6
  import * as Effect from "effect/Effect"
4
- import * as PubSub from "effect/PubSub"
7
+ import type * as HashSet from "effect/HashSet"
5
8
  import * as Layer from "effect/Layer"
6
- import * as Queue from "effect/Queue"
7
- import * as Stream from "effect/Stream"
8
- import type { RunningShard } from "dfx/DiscordGateway/Shard"
9
- import { SharedLive, Sharder } from "dfx/DiscordGateway/Sharder"
10
- import type * as Discord from "dfx/types"
11
- import * as EffectUtils from "dfx/utils/Effect"
12
- import * as Schedule from "effect/Schedule"
13
-
14
- const fromDispatchFactory =
15
- <R, E>(
16
- source: Stream.Stream<R, E, Discord.GatewayPayload<Discord.ReceiveEvent>>,
17
- ) =>
18
- <K extends keyof Discord.ReceiveEvents>(
19
- event: K,
20
- ): Stream.Stream<R, E, Discord.ReceiveEvents[K]> =>
21
- Stream.map(
22
- Stream.filter(source, p => p.t === event),
23
- p => p.d! as any,
24
- )
9
+ import type * as Stream from "effect/Stream"
25
10
 
26
- const handleDispatchFactory =
27
- (hub: PubSub.PubSub<Discord.GatewayPayload<Discord.ReceiveEvent>>) =>
28
- <K extends keyof Discord.ReceiveEvents, R, E, A>(
29
- event: K,
30
- handle: (event: Discord.ReceiveEvents[K]) => Effect.Effect<R, E, A>,
31
- ): Effect.Effect<R, E, never> =>
32
- EffectUtils.subscribeForEachPar(hub, _ => {
33
- if (_.t === event) {
34
- return handle(_.d as any)
35
- }
36
- return Effect.unit as any
37
- })
11
+ export const TypeId = Symbol.for("dfx/DiscordGateway")
12
+ export type TypeId = typeof TypeId
38
13
 
39
14
  export interface DiscordGateway {
15
+ readonly [TypeId]: TypeId
16
+
40
17
  readonly dispatch: Stream.Stream<
41
18
  never,
42
19
  never,
@@ -54,50 +31,24 @@ export interface DiscordGateway {
54
31
  ) => Effect.Effect<never, never, boolean>
55
32
  readonly shards: Effect.Effect<never, never, HashSet.HashSet<RunningShard>>
56
33
  }
57
- export const DiscordGateway = Tag<DiscordGateway>()
34
+
35
+ export const DiscordGateway = Tag<DiscordGateway>(TypeId)
58
36
 
59
37
  export const make = Effect.gen(function* (_) {
60
38
  const sharder = yield* _(Sharder)
61
- const hub = yield* _(
62
- PubSub.unbounded<Discord.GatewayPayload<Discord.ReceiveEvent>>(),
63
- )
64
-
65
- const sendQueue = yield* _(
66
- Queue.unbounded<Discord.GatewayPayload<Discord.SendEvent>>(),
67
- )
68
- const send = (payload: Discord.GatewayPayload<Discord.SendEvent>) =>
69
- sendQueue.offer(payload)
70
-
71
- const dispatch = Stream.fromPubSub(hub)
72
- const fromDispatch = fromDispatchFactory(dispatch)
73
- const handleDispatch = handleDispatchFactory(hub)
74
-
75
- yield* _(
76
- sharder.run(hub, sendQueue),
77
- Effect.tapErrorCause(_ => Effect.logError("fatal error, restarting", _)),
78
- Effect.retry(
79
- Schedule.exponential("1 seconds").pipe(
80
- Schedule.union(Schedule.spaced("30 seconds")),
81
- ),
82
- ),
83
- Effect.forkScoped,
84
- )
39
+ const messaging = yield* _(Messaging)
85
40
 
86
41
  return DiscordGateway.of({
87
- dispatch,
88
- fromDispatch,
89
- handleDispatch,
90
- send,
42
+ [TypeId]: TypeId,
43
+ dispatch: messaging.dispatch,
44
+ fromDispatch: messaging.fromDispatch,
45
+ handleDispatch: messaging.handleDispatch,
46
+ send: messaging.send,
91
47
  shards: sharder.shards,
92
48
  })
93
- }).pipe(
94
- Effect.annotateLogs({
95
- package: "dfx",
96
- service: "DiscordGateway",
97
- }),
98
- )
49
+ })
99
50
 
100
- export const DiscordGatewayLive = Layer.provide(
101
- Layer.scoped(DiscordGateway, make),
102
- SharedLive,
51
+ export const DiscordGatewayLive = Layer.effect(DiscordGateway, make).pipe(
52
+ Layer.provide(MesssagingLive),
53
+ Layer.provide(SharderLive),
103
54
  )